| /* |
| * |
| * 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 implements unit tests for CHIP Interaction Model Command Interaction |
| * |
| */ |
| |
| #include <cinttypes> |
| #include <optional> |
| |
| #include <pw_unit_test/framework.h> |
| |
| #include <app/AppConfig.h> |
| #include <app/CommandHandlerImpl.h> |
| #include <app/InteractionModelEngine.h> |
| #include <app/data-model-provider/ActionReturnStatus.h> |
| #include <app/data-model/Encode.h> |
| #include <app/tests/AppTestContext.h> |
| #include <app/tests/test-interaction-model-api.h> |
| #include <lib/core/CHIPCore.h> |
| #include <lib/core/ErrorStr.h> |
| #include <lib/core/Optional.h> |
| #include <lib/core/StringBuilderAdapters.h> |
| #include <lib/core/TLV.h> |
| #include <lib/core/TLVDebug.h> |
| #include <lib/core/TLVUtilities.h> |
| #include <lib/support/logging/CHIPLogging.h> |
| #include <lib/support/tests/ExtraPwTestMacros.h> |
| #include <messaging/ExchangeContext.h> |
| #include <messaging/ExchangeMgr.h> |
| #include <messaging/Flags.h> |
| #include <platform/CHIPDeviceLayer.h> |
| #include <protocols/interaction_model/Constants.h> |
| #include <system/SystemPacketBuffer.h> |
| #include <system/TLVPacketBufferBackingStore.h> |
| |
| using namespace chip::Protocols; |
| |
| namespace { |
| |
| void CheckForInvalidAction(chip::Test::MessageCapturer & messageLog) |
| { |
| EXPECT_EQ(messageLog.MessageCount(), 1u); |
| EXPECT_TRUE(messageLog.IsMessageType(0, chip::Protocols::InteractionModel::MsgType::StatusResponse)); |
| CHIP_ERROR status; |
| EXPECT_EQ(chip::app::StatusResponse::ProcessStatusResponse(std::move(messageLog.MessagePayload(0)), status), CHIP_NO_ERROR); |
| EXPECT_EQ(status, CHIP_IM_GLOBAL_STATUS(InvalidAction)); |
| } |
| |
| } // anonymous namespace |
| |
| namespace chip { |
| |
| namespace { |
| bool isCommandDispatched = false; |
| size_t commandDispatchedCount = 0; |
| |
| bool sendResponse = true; |
| bool asyncCommand = false; |
| |
| constexpr EndpointId kTestEndpointId = 1; |
| constexpr ClusterId kTestClusterId = 3; |
| constexpr CommandId kTestCommandIdWithData = 4; |
| constexpr CommandId kTestCommandIdNoData = 5; |
| constexpr CommandId kTestCommandIdCommandSpecificResponse = 6; |
| constexpr CommandId kTestCommandIdFillResponseMessage = 7; |
| constexpr CommandId kTestNonExistCommandId = 0; |
| |
| const app::CommandSender::TestOnlyMarker kCommandSenderTestOnlyMarker; |
| |
| class SimpleTLVPayload : public app::DataModel::EncodableToTLV |
| { |
| public: |
| CHIP_ERROR EncodeTo(TLV::TLVWriter & aWriter, TLV::Tag aTag) const override |
| { |
| TLV::TLVType outerType; |
| ReturnErrorOnFailure(aWriter.StartContainer(aTag, TLV::kTLVType_Structure, outerType)); |
| ReturnErrorOnFailure(aWriter.PutBoolean(chip::TLV::ContextTag(1), true)); |
| return aWriter.EndContainer(outerType); |
| } |
| }; |
| |
| const chip::Test::MockNodeConfig & TestMockNodeConfig() |
| { |
| using namespace chip::app; |
| using namespace chip::Test; |
| using namespace chip::app::Clusters::Globals::Attributes; |
| |
| // clang-format off |
| static const MockNodeConfig config({ |
| MockEndpointConfig(chip::kTestEndpointId, { |
| MockClusterConfig(Clusters::Identify::Id, { |
| ClusterRevision::Id, FeatureMap::Id, |
| }, |
| {}, // events |
| { |
| kTestCommandIdWithData, |
| kTestCommandIdNoData, |
| kTestCommandIdCommandSpecificResponse, |
| kTestCommandIdFillResponseMessage, |
| }, // accepted commands |
| {} // generated commands |
| ), |
| }), |
| }); |
| // clang-format on |
| return config; |
| } |
| |
| } // namespace |
| |
| namespace app { |
| |
| CommandHandler::Handle asyncCommandHandle; |
| |
| enum class ForcedSizeBufferLengthHint |
| { |
| kSizeBetween0and255, |
| kSizeGreaterThan255, |
| }; |
| |
| class ForcedSizeBuffer : public app::DataModel::EncodableToTLV |
| { |
| public: |
| ForcedSizeBuffer(uint32_t size) |
| { |
| if (mBuffer.Alloc(size)) |
| { |
| // No significance with using 0x12, just using a value. |
| memset(mBuffer.Get(), 0x12, size); |
| } |
| } |
| |
| // No significance with using 0x12 as the CommandId, just using a value. |
| static constexpr chip::CommandId GetCommandId() { return 0x12; } |
| CHIP_ERROR EncodeTo(TLV::TLVWriter & aWriter, TLV::Tag aTag) const override |
| { |
| VerifyOrReturnError(mBuffer, CHIP_ERROR_NO_MEMORY); |
| |
| TLV::TLVType outerContainerType; |
| ReturnErrorOnFailure(aWriter.StartContainer(aTag, TLV::kTLVType_Structure, outerContainerType)); |
| ReturnErrorOnFailure(app::DataModel::Encode(aWriter, TLV::ContextTag(1), ByteSpan(mBuffer.Get(), mBuffer.AllocatedSize()))); |
| return aWriter.EndContainer(outerContainerType); |
| } |
| |
| private: |
| chip::Platform::ScopedMemoryBufferWithSize<uint8_t> mBuffer; |
| }; |
| |
| struct Fields |
| { |
| static constexpr chip::CommandId GetCommandId() { return 4; } |
| CHIP_ERROR Encode(TLV::TLVWriter & aWriter, TLV::Tag aTag) const |
| { |
| TLV::TLVType outerContainerType; |
| ReturnErrorOnFailure(aWriter.StartContainer(aTag, TLV::kTLVType_Structure, outerContainerType)); |
| ReturnErrorOnFailure(aWriter.PutBoolean(TLV::ContextTag(1), true)); |
| return aWriter.EndContainer(outerContainerType); |
| } |
| }; |
| |
| struct BadFields |
| { |
| static constexpr chip::CommandId GetCommandId() { return 4; } |
| CHIP_ERROR Encode(TLV::TLVWriter & aWriter, TLV::Tag aTag) const |
| { |
| TLV::TLVType outerContainerType; |
| uint8_t data[36] = { 0 }; |
| ReturnErrorOnFailure(aWriter.StartContainer(aTag, TLV::kTLVType_Structure, outerContainerType)); |
| // Just encode something bad to return a failure state here. |
| for (uint8_t i = 1; i < UINT8_MAX; i++) |
| { |
| ReturnErrorOnFailure(app::DataModel::Encode(aWriter, TLV::ContextTag(i), ByteSpan(data))); |
| } |
| return aWriter.EndContainer(outerContainerType); |
| } |
| }; |
| |
| static Protocols::InteractionModel::Status ServerClusterCommandExists(const ConcreteCommandPath & aRequestCommandPath) |
| { |
| // Mock cluster catalog, only support commands on one cluster on one endpoint. |
| if (aRequestCommandPath.mEndpointId != kTestEndpointId) |
| { |
| return Protocols::InteractionModel::Status::UnsupportedEndpoint; |
| } |
| |
| if (aRequestCommandPath.mClusterId != kTestClusterId) |
| { |
| return Protocols::InteractionModel::Status::UnsupportedCluster; |
| } |
| |
| if (aRequestCommandPath.mCommandId == kTestNonExistCommandId) |
| { |
| return Protocols::InteractionModel::Status::UnsupportedCommand; |
| } |
| |
| return Protocols::InteractionModel::Status::Success; |
| } |
| |
| void DispatchSingleClusterCommand(const ConcreteCommandPath & aRequestCommandPath, chip::TLV::TLVReader & aReader, |
| CommandHandler * apCommandObj) |
| { |
| ChipLogDetail(Controller, "Received Cluster Command: Endpoint=%x Cluster=" ChipLogFormatMEI " Command=" ChipLogFormatMEI, |
| aRequestCommandPath.mEndpointId, ChipLogValueMEI(aRequestCommandPath.mClusterId), |
| ChipLogValueMEI(aRequestCommandPath.mCommandId)); |
| |
| // Duplicate what our normal command-field-decode code does, in terms of |
| // checking for a struct and then entering it before getting the fields. |
| if (aReader.GetType() != TLV::kTLVType_Structure) |
| { |
| apCommandObj->AddStatus(aRequestCommandPath, Protocols::InteractionModel::Status::InvalidAction); |
| return; |
| } |
| |
| TLV::TLVType outerContainerType; |
| EXPECT_EQ(aReader.EnterContainer(outerContainerType), CHIP_NO_ERROR); |
| |
| CHIP_ERROR err = aReader.Next(); |
| if (aRequestCommandPath.mCommandId == kTestCommandIdNoData) |
| { |
| EXPECT_EQ(err, CHIP_ERROR_END_OF_TLV); |
| } |
| else |
| { |
| EXPECT_EQ(err, CHIP_NO_ERROR); |
| EXPECT_EQ(aReader.GetTag(), TLV::ContextTag(1)); |
| |
| bool val; |
| EXPECT_EQ(aReader.Get(val), CHIP_NO_ERROR); |
| EXPECT_TRUE(val); |
| } |
| |
| EXPECT_EQ(aReader.ExitContainer(outerContainerType), CHIP_NO_ERROR); |
| |
| if (asyncCommand) |
| { |
| asyncCommandHandle = apCommandObj; |
| asyncCommand = false; |
| } |
| |
| if (sendResponse) |
| { |
| if (aRequestCommandPath.mCommandId == kTestCommandIdNoData || aRequestCommandPath.mCommandId == kTestCommandIdWithData) |
| { |
| apCommandObj->AddStatus(aRequestCommandPath, Protocols::InteractionModel::Status::Success); |
| } |
| else |
| { |
| SimpleTLVPayload payloadWriter; |
| apCommandObj->AddResponse(aRequestCommandPath, aRequestCommandPath.mCommandId, payloadWriter); |
| } |
| } |
| |
| chip::isCommandDispatched = true; |
| commandDispatchedCount++; |
| } |
| |
| class MockCommandSenderCallback : public CommandSender::Callback |
| { |
| public: |
| void OnResponse(chip::app::CommandSender * apCommandSender, const chip::app::ConcreteCommandPath & aPath, |
| const chip::app::StatusIB & aStatus, chip::TLV::TLVReader * aData) override |
| { |
| IgnoreUnusedVariable(apCommandSender); |
| IgnoreUnusedVariable(aData); |
| ChipLogDetail(Controller, "Received Cluster Command: Cluster=%" PRIx32 " Command=%" PRIx32 " Endpoint=%x", aPath.mClusterId, |
| aPath.mCommandId, aPath.mEndpointId); |
| onResponseCalledTimes++; |
| } |
| void OnError(const chip::app::CommandSender * apCommandSender, CHIP_ERROR aError) override |
| { |
| ChipLogError(Controller, "OnError happens with %" CHIP_ERROR_FORMAT, aError.Format()); |
| mError = aError; |
| onErrorCalledTimes++; |
| mError = aError; |
| } |
| void OnDone(chip::app::CommandSender * apCommandSender) override { onFinalCalledTimes++; } |
| |
| void ResetCounter() |
| { |
| onResponseCalledTimes = 0; |
| onErrorCalledTimes = 0; |
| onFinalCalledTimes = 0; |
| } |
| |
| int onResponseCalledTimes = 0; |
| int onErrorCalledTimes = 0; |
| int onFinalCalledTimes = 0; |
| CHIP_ERROR mError = CHIP_NO_ERROR; |
| } mockCommandSenderDelegate; |
| |
| class MockCommandSenderExtendableCallback : public CommandSender::ExtendableCallback |
| { |
| public: |
| void OnResponse(CommandSender * apCommandSender, const CommandSender::ResponseData & aResponseData) override |
| { |
| IgnoreUnusedVariable(apCommandSender); |
| ChipLogDetail(Controller, "Received response for command: Cluster=%" PRIx32 " Command=%" PRIx32 " Endpoint=%x", |
| aResponseData.path.mClusterId, aResponseData.path.mCommandId, aResponseData.path.mEndpointId); |
| onResponseCalledTimes++; |
| } |
| void OnNoResponse(CommandSender * commandSender, const CommandSender::NoResponseData & aNoResponseData) override |
| { |
| ChipLogError(Controller, "NoResponse received for command associated with CommandRef %u", aNoResponseData.commandRef); |
| onNoResponseCalledTimes++; |
| } |
| void OnError(const CommandSender * apCommandSender, const CommandSender::ErrorData & aErrorData) override |
| { |
| ChipLogError(Controller, "OnError happens with %" CHIP_ERROR_FORMAT, aErrorData.error.Format()); |
| mError = aErrorData.error; |
| onErrorCalledTimes++; |
| } |
| void OnDone(CommandSender * apCommandSender) override { onFinalCalledTimes++; } |
| |
| void ResetCounter() |
| { |
| onResponseCalledTimes = 0; |
| onNoResponseCalledTimes = 0; |
| onErrorCalledTimes = 0; |
| onFinalCalledTimes = 0; |
| } |
| |
| int onResponseCalledTimes = 0; |
| int onNoResponseCalledTimes = 0; |
| int onErrorCalledTimes = 0; |
| int onFinalCalledTimes = 0; |
| CHIP_ERROR mError = CHIP_NO_ERROR; |
| } mockCommandSenderExtendedDelegate; |
| |
| class MockCommandResponder : public CommandHandlerExchangeInterface |
| { |
| public: |
| Messaging::ExchangeContext * GetExchangeContext() const override { return nullptr; } |
| void HandlingSlowCommand() override {} |
| Access::SubjectDescriptor GetSubjectDescriptor() const override { return Access::SubjectDescriptor(); } |
| FabricIndex GetAccessingFabricIndex() const override { return kUndefinedFabricIndex; } |
| |
| Optional<GroupId> GetGroupId() const override { return NullOptional; } |
| |
| void AddInvokeResponseToSend(System::PacketBufferHandle && aPacket) override { mChunks.AddToEnd(std::move(aPacket)); } |
| void ResponseDropped() override { mResponseDropped = true; } |
| |
| size_t GetCommandResponseMaxBufferSize() override { return kMaxSecureSduLengthBytes; } |
| |
| System::PacketBufferHandle mChunks; |
| bool mResponseDropped = false; |
| }; |
| |
| class MockCommandHandlerCallback : public CommandHandlerImpl::Callback |
| { |
| public: |
| void OnDone(CommandHandlerImpl & apCommandHandler) final { onFinalCalledTimes++; } |
| void DispatchCommand(CommandHandlerImpl & apCommandObj, const ConcreteCommandPath & aCommandPath, |
| TLV::TLVReader & apPayload) final |
| { |
| DispatchSingleClusterCommand(aCommandPath, apPayload, &apCommandObj); |
| } |
| |
| Protocols::InteractionModel::Status ValidateCommandCanBeDispatched(const DataModel::InvokeRequest & request) override |
| { |
| using Protocols::InteractionModel::Status; |
| |
| Status status = ServerClusterCommandExists(request.path); |
| if (status != Status::Success) |
| { |
| return status; |
| } |
| |
| // NOTE: IM does more validation here, however for now we do minimal options |
| // to pass the test. |
| |
| return Status::Success; |
| } |
| |
| void ResetCounter() { onFinalCalledTimes = 0; } |
| |
| int onFinalCalledTimes = 0; |
| } mockCommandHandlerDelegate; |
| |
| class TestCommandInteractionModel : public TestImCustomDataModel |
| { |
| public: |
| static TestCommandInteractionModel * Instance() |
| { |
| static TestCommandInteractionModel instance; |
| return &instance; |
| } |
| |
| TestCommandInteractionModel() {} |
| |
| std::optional<DataModel::ActionReturnStatus> Invoke(const DataModel::InvokeRequest & request, |
| chip::TLV::TLVReader & input_arguments, CommandHandler * handler) |
| { |
| DispatchSingleClusterCommand(request.path, input_arguments, handler); |
| return std::nullopt; // handler status is set by the dispatch |
| } |
| }; |
| |
| class TestCommandInteraction : public chip::Test::AppContext |
| { |
| public: |
| void SetUp() override |
| { |
| AppContext::SetUp(); |
| mOldProvider = InteractionModelEngine::GetInstance()->SetDataModelProvider(TestCommandInteractionModel::Instance()); |
| chip::Test::SetMockNodeConfig(TestMockNodeConfig()); |
| } |
| |
| void TearDown() override |
| { |
| chip::Test::ResetMockNodeConfig(); |
| InteractionModelEngine::GetInstance()->SetDataModelProvider(mOldProvider); |
| AppContext::TearDown(); |
| } |
| |
| static size_t GetNumActiveCommandResponderObjects() |
| { |
| return chip::app::InteractionModelEngine::GetInstance()->mCommandResponderObjs.Allocated(); |
| } |
| |
| void TestCommandInvalidMessage1(); |
| void TestCommandInvalidMessage2(); |
| void TestCommandInvalidMessage3(); |
| void TestCommandInvalidMessage4(); |
| void TestCommandSender_WithSendCommand(); |
| void TestCommandSender_WithProcessReceivedMsg(); |
| void TestCommandSender_ExtendableApiWithProcessReceivedMsg(); |
| void TestCommandSender_ExtendableApiWithProcessReceivedMsgContainingInvalidCommandRef(); |
| void TestCommandSender_ValidateSecondLargeAddRequestDataRollbacked(); |
| void TestCommandHandler_WithoutResponderCallingAddStatus(); |
| void TestCommandHandler_WithoutResponderCallingAddResponse(); |
| void TestCommandHandler_WithoutResponderCallingDirectPrepareFinishCommandApis(); |
| void TestCommandHandler_RejectsMultipleCommandsWithIdenticalCommandRef(); |
| void TestCommandHandler_RejectMultipleCommandsWhenHandlerOnlySupportsOne(); |
| void TestCommandHandler_AcceptMultipleCommands(); |
| void TestCommandHandler_FillUpInvokeResponseMessageWhereSecondResponseIsStatusResponse(); |
| void TestCommandHandler_FillUpInvokeResponseMessageWhereSecondResponseIsDataResponsePrimative(); |
| void TestCommandHandler_FillUpInvokeResponseMessageWhereSecondResponseIsDataResponse(); |
| void TestCommandHandler_ReleaseWithExchangeClosed(); |
| |
| /** |
| * With the introduction of batch invoke commands, CommandHandler keeps track of incoming |
| * ConcreteCommandPath and the associated CommandRefs. These are normally populated |
| * as part of OnInvokeCommandRequest from the incoming request. For some unit tests where |
| * we want to test APIs that cluster code uses, we need to inject entries into the |
| * CommandPathRegistry directly. |
| */ |
| class CommandHandlerWithUnrespondedCommand : public app::CommandHandlerImpl |
| { |
| public: |
| CommandHandlerWithUnrespondedCommand(CommandHandlerImpl::Callback * apCallback, |
| const ConcreteCommandPath & aRequestCommandPath, const Optional<uint16_t> & aRef) : |
| CommandHandlerImpl(apCallback) |
| { |
| GetCommandPathRegistry().Add(aRequestCommandPath, aRef.std_optional()); |
| SetExchangeInterface(&mMockCommandResponder); |
| } |
| MockCommandResponder mMockCommandResponder; |
| |
| using app::CommandHandler::AddResponse; |
| }; |
| |
| // Generate an invoke request. If aCommandId is kTestCommandIdWithData, a |
| // payload will be included. Otherwise no payload will be included. |
| static void GenerateInvokeRequest(System::PacketBufferHandle & aPayload, bool aIsTimedRequest, CommandId aCommandId, |
| ClusterId aClusterId = kTestClusterId, EndpointId aEndpointId = kTestEndpointId); |
| // Generate an invoke response. If aCommandId is kTestCommandIdWithData, a |
| // payload will be included. Otherwise no payload will be included. |
| static void GenerateInvokeResponse(System::PacketBufferHandle & aPayload, CommandId aCommandId, |
| ClusterId aClusterId = kTestClusterId, EndpointId aEndpointId = kTestEndpointId, |
| std::optional<uint16_t> aCommandRef = std::nullopt); |
| static void AddInvokeRequestData(CommandSender * apCommandSender, CommandId aCommandId = kTestCommandIdWithData); |
| static void AddInvalidInvokeRequestData(CommandSender * apCommandSender, CommandId aCommandId = kTestCommandIdWithData); |
| static void AddInvokeResponseData(CommandHandler * apCommandHandler, bool aNeedStatusCode, |
| CommandId aResponseCommandId = kTestCommandIdWithData, |
| CommandId aRequestCommandId = kTestCommandIdWithData); |
| static uint32_t GetAddResponseDataOverheadSizeForPath(const ConcreteCommandPath & aRequestCommandPath, |
| ForcedSizeBufferLengthHint aBufferSizeHint); |
| static void FillCurrentInvokeResponseBuffer(CommandHandlerImpl * apCommandHandler, |
| const ConcreteCommandPath & aRequestCommandPath, uint32_t aSizeToLeaveInBuffer); |
| static void ValidateCommandHandlerEncodeInvokeResponseMessage(bool aNeedStatusCode); |
| |
| protected: |
| chip::app::DataModel::Provider * mOldProvider = nullptr; |
| }; |
| |
| class TestExchangeDelegate : public Messaging::ExchangeDelegate |
| { |
| CHIP_ERROR OnMessageReceived(Messaging::ExchangeContext * ec, const PayloadHeader & payloadHeader, |
| System::PacketBufferHandle && payload) override |
| { |
| return CHIP_NO_ERROR; |
| } |
| |
| void OnResponseTimeout(Messaging::ExchangeContext * ec) override {} |
| }; |
| |
| CommandPathParams MakeTestCommandPath(CommandId aCommandId = kTestCommandIdWithData) |
| { |
| return CommandPathParams(kTestEndpointId, 0, kTestClusterId, aCommandId, (chip::app::CommandPathFlags::kEndpointIdValid)); |
| } |
| |
| void TestCommandInteraction::GenerateInvokeRequest(System::PacketBufferHandle & aPayload, bool aIsTimedRequest, |
| CommandId aCommandId, ClusterId aClusterId, EndpointId aEndpointId) |
| |
| { |
| InvokeRequestMessage::Builder invokeRequestMessageBuilder; |
| System::PacketBufferTLVWriter writer; |
| writer.Init(std::move(aPayload)); |
| |
| EXPECT_EQ(invokeRequestMessageBuilder.Init(&writer), CHIP_NO_ERROR); |
| |
| invokeRequestMessageBuilder.SuppressResponse(true).TimedRequest(aIsTimedRequest); |
| InvokeRequests::Builder & invokeRequests = invokeRequestMessageBuilder.CreateInvokeRequests(); |
| ASSERT_EQ(invokeRequestMessageBuilder.GetError(), CHIP_NO_ERROR); |
| |
| CommandDataIB::Builder & commandDataIBBuilder = invokeRequests.CreateCommandData(); |
| EXPECT_EQ(invokeRequests.GetError(), CHIP_NO_ERROR); |
| |
| CommandPathIB::Builder & commandPathBuilder = commandDataIBBuilder.CreatePath(); |
| EXPECT_EQ(commandDataIBBuilder.GetError(), CHIP_NO_ERROR); |
| |
| commandPathBuilder.EndpointId(aEndpointId).ClusterId(aClusterId).CommandId(aCommandId).EndOfCommandPathIB(); |
| EXPECT_EQ(commandPathBuilder.GetError(), CHIP_NO_ERROR); |
| |
| if (aCommandId == kTestCommandIdWithData) |
| { |
| chip::TLV::TLVWriter * pWriter = commandDataIBBuilder.GetWriter(); |
| chip::TLV::TLVType dummyType = chip::TLV::kTLVType_NotSpecified; |
| EXPECT_EQ(pWriter->StartContainer(chip::TLV::ContextTag(chip::to_underlying(CommandDataIB::Tag::kFields)), |
| chip::TLV::kTLVType_Structure, dummyType), |
| CHIP_NO_ERROR); |
| |
| EXPECT_EQ(pWriter->PutBoolean(chip::TLV::ContextTag(1), true), CHIP_NO_ERROR); |
| |
| EXPECT_EQ(pWriter->EndContainer(dummyType), CHIP_NO_ERROR); |
| } |
| |
| commandDataIBBuilder.EndOfCommandDataIB(); |
| EXPECT_EQ(commandDataIBBuilder.GetError(), CHIP_NO_ERROR); |
| |
| invokeRequests.EndOfInvokeRequests(); |
| EXPECT_EQ(invokeRequests.GetError(), CHIP_NO_ERROR); |
| |
| invokeRequestMessageBuilder.EndOfInvokeRequestMessage(); |
| ASSERT_EQ(invokeRequestMessageBuilder.GetError(), CHIP_NO_ERROR); |
| |
| EXPECT_EQ(writer.Finalize(&aPayload), CHIP_NO_ERROR); |
| } |
| |
| void TestCommandInteraction::GenerateInvokeResponse(System::PacketBufferHandle & aPayload, CommandId aCommandId, |
| ClusterId aClusterId, EndpointId aEndpointId, |
| std::optional<uint16_t> aCommandRef) |
| |
| { |
| InvokeResponseMessage::Builder invokeResponseMessageBuilder; |
| System::PacketBufferTLVWriter writer; |
| writer.Init(std::move(aPayload)); |
| |
| EXPECT_EQ(invokeResponseMessageBuilder.Init(&writer), CHIP_NO_ERROR); |
| |
| invokeResponseMessageBuilder.SuppressResponse(true); |
| InvokeResponseIBs::Builder & invokeResponses = invokeResponseMessageBuilder.CreateInvokeResponses(); |
| EXPECT_EQ(invokeResponseMessageBuilder.GetError(), CHIP_NO_ERROR); |
| |
| InvokeResponseIB::Builder & invokeResponseIBBuilder = invokeResponses.CreateInvokeResponse(); |
| EXPECT_EQ(invokeResponses.GetError(), CHIP_NO_ERROR); |
| |
| CommandDataIB::Builder & commandDataIBBuilder = invokeResponseIBBuilder.CreateCommand(); |
| EXPECT_EQ(commandDataIBBuilder.GetError(), CHIP_NO_ERROR); |
| |
| CommandPathIB::Builder & commandPathBuilder = commandDataIBBuilder.CreatePath(); |
| EXPECT_EQ(commandDataIBBuilder.GetError(), CHIP_NO_ERROR); |
| |
| commandPathBuilder.EndpointId(aEndpointId).ClusterId(aClusterId).CommandId(aCommandId).EndOfCommandPathIB(); |
| EXPECT_EQ(commandPathBuilder.GetError(), CHIP_NO_ERROR); |
| |
| if (aCommandId == kTestCommandIdWithData) |
| { |
| chip::TLV::TLVWriter * pWriter = commandDataIBBuilder.GetWriter(); |
| chip::TLV::TLVType dummyType = chip::TLV::kTLVType_NotSpecified; |
| |
| EXPECT_EQ(pWriter->StartContainer(chip::TLV::ContextTag(chip::to_underlying(CommandDataIB::Tag::kFields)), |
| chip::TLV::kTLVType_Structure, dummyType), |
| CHIP_NO_ERROR); |
| |
| EXPECT_EQ(pWriter->PutBoolean(chip::TLV::ContextTag(1), true), CHIP_NO_ERROR); |
| |
| EXPECT_EQ(pWriter->EndContainer(dummyType), CHIP_NO_ERROR); |
| } |
| |
| if (aCommandRef.has_value()) |
| { |
| EXPECT_EQ(commandDataIBBuilder.Ref(*aCommandRef), CHIP_NO_ERROR); |
| } |
| |
| commandDataIBBuilder.EndOfCommandDataIB(); |
| EXPECT_EQ(commandDataIBBuilder.GetError(), CHIP_NO_ERROR); |
| |
| invokeResponseIBBuilder.EndOfInvokeResponseIB(); |
| EXPECT_EQ(invokeResponseIBBuilder.GetError(), CHIP_NO_ERROR); |
| |
| invokeResponses.EndOfInvokeResponses(); |
| EXPECT_EQ(invokeResponses.GetError(), CHIP_NO_ERROR); |
| |
| invokeResponseMessageBuilder.EndOfInvokeResponseMessage(); |
| EXPECT_EQ(invokeResponseMessageBuilder.GetError(), CHIP_NO_ERROR); |
| |
| EXPECT_EQ(writer.Finalize(&aPayload), CHIP_NO_ERROR); |
| } |
| |
| void TestCommandInteraction::AddInvokeRequestData(CommandSender * apCommandSender, CommandId aCommandId) |
| { |
| auto commandPathParams = MakeTestCommandPath(aCommandId); |
| |
| EXPECT_EQ(apCommandSender->PrepareCommand(commandPathParams), CHIP_NO_ERROR); |
| |
| chip::TLV::TLVWriter * writer = apCommandSender->GetCommandDataIBTLVWriter(); |
| |
| EXPECT_EQ(writer->PutBoolean(chip::TLV::ContextTag(1), true), CHIP_NO_ERROR); |
| EXPECT_EQ(apCommandSender->FinishCommand(), CHIP_NO_ERROR); |
| } |
| |
| void TestCommandInteraction::AddInvalidInvokeRequestData(CommandSender * apCommandSender, CommandId aCommandId) |
| { |
| auto commandPathParams = MakeTestCommandPath(aCommandId); |
| |
| EXPECT_EQ(apCommandSender->PrepareCommand(commandPathParams), CHIP_NO_ERROR); |
| |
| chip::TLV::TLVWriter * writer = apCommandSender->GetCommandDataIBTLVWriter(); |
| |
| EXPECT_EQ(writer->PutBoolean(chip::TLV::ContextTag(1), true), CHIP_NO_ERROR); |
| apCommandSender->MoveToState(CommandSender::State::AddedCommand); |
| } |
| |
| void TestCommandInteraction::AddInvokeResponseData(CommandHandler * apCommandHandler, bool aNeedStatusCode, |
| CommandId aResponseCommandId, CommandId aRequestCommandId) |
| { |
| constexpr EndpointId kTestEndpointId = 1; |
| constexpr ClusterId kTestClusterId = 3; |
| ConcreteCommandPath requestCommandPath = { kTestEndpointId, kTestClusterId, aRequestCommandId }; |
| if (aNeedStatusCode) |
| { |
| apCommandHandler->AddStatus(requestCommandPath, Protocols::InteractionModel::Status::Success); |
| } |
| else |
| { |
| SimpleTLVPayload payloadWriter; |
| EXPECT_EQ(apCommandHandler->AddResponseData(requestCommandPath, aResponseCommandId, payloadWriter), CHIP_NO_ERROR); |
| } |
| } |
| |
| uint32_t TestCommandInteraction::GetAddResponseDataOverheadSizeForPath(const ConcreteCommandPath & aRequestCommandPath, |
| ForcedSizeBufferLengthHint aBufferSizeHint) |
| { |
| BasicCommandPathRegistry<4> basicCommandPathRegistry; |
| MockCommandResponder mockCommandResponder; |
| CommandHandlerImpl::TestOnlyOverrides testOnlyOverrides{ &basicCommandPathRegistry, &mockCommandResponder }; |
| CommandHandlerImpl commandHandler(testOnlyOverrides, &mockCommandHandlerDelegate); |
| commandHandler.mReserveSpaceForMoreChunkMessages = true; |
| ConcreteCommandPath requestCommandPath1 = { kTestEndpointId, kTestClusterId, kTestCommandIdFillResponseMessage }; |
| ConcreteCommandPath requestCommandPath2 = { kTestEndpointId, kTestClusterId, kTestCommandIdCommandSpecificResponse }; |
| |
| EXPECT_EQ(basicCommandPathRegistry.Add(requestCommandPath1, std::make_optional<uint16_t>(static_cast<uint16_t>(1))), |
| CHIP_NO_ERROR); |
| |
| EXPECT_EQ(basicCommandPathRegistry.Add(requestCommandPath2, std::make_optional<uint16_t>(static_cast<uint16_t>(2))), |
| CHIP_NO_ERROR); |
| |
| EXPECT_EQ(commandHandler.AllocateBuffer(), CHIP_NO_ERROR); |
| |
| uint32_t remainingSizeBefore = commandHandler.mInvokeResponseBuilder.GetWriter()->GetRemainingFreeLength(); |
| |
| // When ForcedSizeBuffer exceeds 255, an extra byte is needed for length, affecting the overhead size required by |
| // AddResponseData. In order to have this accounted for in overhead calculation we set the length to be 256. |
| uint32_t sizeOfForcedSizeBuffer = aBufferSizeHint == ForcedSizeBufferLengthHint::kSizeGreaterThan255 ? 256 : 0; |
| ForcedSizeBuffer responseData(sizeOfForcedSizeBuffer); |
| EXPECT_EQ(commandHandler.AddResponseData(aRequestCommandPath, responseData.GetCommandId(), responseData), CHIP_NO_ERROR); |
| uint32_t remainingSizeAfter = commandHandler.mInvokeResponseBuilder.GetWriter()->GetRemainingFreeLength(); |
| uint32_t delta = remainingSizeBefore - remainingSizeAfter - sizeOfForcedSizeBuffer; |
| |
| return delta; |
| } |
| |
| void TestCommandInteraction::FillCurrentInvokeResponseBuffer(CommandHandlerImpl * apCommandHandler, |
| const ConcreteCommandPath & aRequestCommandPath, |
| uint32_t aSizeToLeaveInBuffer) |
| { |
| EXPECT_EQ(apCommandHandler->AllocateBuffer(), CHIP_NO_ERROR); |
| uint32_t remainingSize = apCommandHandler->mInvokeResponseBuilder.GetWriter()->GetRemainingFreeLength(); |
| |
| // AddResponseData's overhead calculation depends on the size of ForcedSizeBuffer. If the buffer exceeds 255 bytes, an extra |
| // length byte is required. Since tests using FillCurrentInvokeResponseBuffer currently end up with sizeToFill > 255, we |
| // inform the calculation of this expectation. Nonetheless, we also validate this assumption for correctness. |
| ForcedSizeBufferLengthHint bufferSizeHint = ForcedSizeBufferLengthHint::kSizeGreaterThan255; |
| uint32_t overheadSizeNeededForAddingResponse = GetAddResponseDataOverheadSizeForPath(aRequestCommandPath, bufferSizeHint); |
| EXPECT_GT(remainingSize, (aSizeToLeaveInBuffer + overheadSizeNeededForAddingResponse)); |
| uint32_t sizeToFill = remainingSize - aSizeToLeaveInBuffer - overheadSizeNeededForAddingResponse; |
| |
| // Validating assumption. If this fails, it means overheadSizeNeededForAddingResponse is likely too large. |
| EXPECT_GE(sizeToFill, 256u); |
| |
| ForcedSizeBuffer responseData(sizeToFill); |
| EXPECT_EQ(apCommandHandler->AddResponseData(aRequestCommandPath, responseData.GetCommandId(), responseData), CHIP_NO_ERROR); |
| } |
| |
| void TestCommandInteraction::ValidateCommandHandlerEncodeInvokeResponseMessage(bool aNeedStatusCode) |
| { |
| chip::app::ConcreteCommandPath requestCommandPath(kTestEndpointId, kTestClusterId, kTestCommandIdWithData); |
| CommandHandlerWithUnrespondedCommand commandHandler(&mockCommandHandlerDelegate, requestCommandPath, |
| /* aRef = */ NullOptional); |
| |
| { |
| // This simulates how cluster would call CommandHandler APIs synchronously. There would |
| // be handle already acquired on the callers behalf. |
| CommandHandler::Handle handle(&commandHandler); |
| |
| AddInvokeResponseData(&commandHandler, aNeedStatusCode); |
| } |
| EXPECT_FALSE(commandHandler.mMockCommandResponder.mChunks.IsNull()); |
| } |
| |
| // Command Sender sends invoke request, command handler drops invoke response, then test injects status response message with |
| // busy to client, client sends out a status response with invalid action. |
| TEST_F_FROM_FIXTURE(TestCommandInteraction, TestCommandInvalidMessage1) |
| { |
| mockCommandSenderDelegate.ResetCounter(); |
| app::CommandSender commandSender(&mockCommandSenderDelegate, &GetExchangeManager()); |
| |
| AddInvokeRequestData(&commandSender); |
| asyncCommand = false; |
| |
| GetLoopback().mSentMessageCount = 0; |
| GetLoopback().mNumMessagesToDrop = 1; |
| GetLoopback().mNumMessagesToAllowBeforeDropping = 1; |
| EXPECT_EQ(commandSender.SendCommandRequest(GetSessionBobToAlice()), CHIP_NO_ERROR); |
| DrainAndServiceIO(); |
| |
| EXPECT_EQ(GetLoopback().mSentMessageCount, 2u); |
| EXPECT_EQ(GetLoopback().mDroppedMessageCount, 1u); |
| |
| EXPECT_EQ(mockCommandSenderDelegate.onResponseCalledTimes, 0); |
| EXPECT_EQ(mockCommandSenderDelegate.onFinalCalledTimes, 0); |
| EXPECT_EQ(mockCommandSenderDelegate.onErrorCalledTimes, 0); |
| |
| EXPECT_EQ(GetNumActiveCommandResponderObjects(), 0u); |
| |
| System::PacketBufferHandle msgBuf = System::PacketBufferHandle::New(kMaxSecureSduLengthBytes); |
| ASSERT_FALSE(msgBuf.IsNull()); |
| System::PacketBufferTLVWriter writer; |
| writer.Init(std::move(msgBuf)); |
| StatusResponseMessage::Builder response; |
| response.Init(&writer); |
| response.Status(Protocols::InteractionModel::Status::Busy); |
| EXPECT_EQ(writer.Finalize(&msgBuf), CHIP_NO_ERROR); |
| |
| PayloadHeader payloadHeader; |
| payloadHeader.SetExchangeID(0); |
| payloadHeader.SetMessageType(chip::Protocols::InteractionModel::MsgType::StatusResponse); |
| chip::Test::MessageCapturer messageLog(*this); |
| messageLog.mCaptureStandaloneAcks = false; |
| |
| // Since we are dropping packets, things are not getting acked. Set up our |
| // MRP state to look like what it would have looked like if the packet had |
| // not gotten dropped. |
| |
| PretendWeGotReplyFromServer(*this, commandSender.mExchangeCtx.Get()); |
| |
| GetLoopback().mSentMessageCount = 0; |
| GetLoopback().mNumMessagesToDrop = 0; |
| GetLoopback().mNumMessagesToAllowBeforeDropping = 0; |
| GetLoopback().mDroppedMessageCount = 0; |
| |
| EXPECT_EQ(commandSender.OnMessageReceived(commandSender.mExchangeCtx.Get(), payloadHeader, std::move(msgBuf)), |
| CHIP_IM_GLOBAL_STATUS(Busy)); |
| EXPECT_EQ(mockCommandSenderDelegate.mError, CHIP_IM_GLOBAL_STATUS(Busy)); |
| EXPECT_EQ(mockCommandSenderDelegate.onResponseCalledTimes, 0); |
| EXPECT_EQ(mockCommandSenderDelegate.onFinalCalledTimes, 1); |
| EXPECT_EQ(mockCommandSenderDelegate.onErrorCalledTimes, 1); |
| EXPECT_EQ(commandSender.GetInvokeResponseMessageCount(), 0u); |
| |
| DrainAndServiceIO(); |
| |
| // Client sent status report with invalid action, server's exchange has been closed, so all it sent is an MRP Ack |
| EXPECT_EQ(GetLoopback().mSentMessageCount, 2u); |
| CheckForInvalidAction(messageLog); |
| EXPECT_EQ(GetNumActiveCommandResponderObjects(), 0u); |
| ExpireSessionAliceToBob(); |
| ExpireSessionBobToAlice(); |
| CreateSessionAliceToBob(); |
| CreateSessionBobToAlice(); |
| } |
| |
| // Command Sender sends invoke request, command handler drops invoke response, then test injects unknown message to client, |
| // client sends out status response with invalid action. |
| TEST_F_FROM_FIXTURE(TestCommandInteraction, TestCommandInvalidMessage2) |
| { |
| mockCommandSenderDelegate.ResetCounter(); |
| app::CommandSender commandSender(&mockCommandSenderDelegate, &GetExchangeManager()); |
| |
| AddInvokeRequestData(&commandSender); |
| asyncCommand = false; |
| |
| GetLoopback().mSentMessageCount = 0; |
| GetLoopback().mNumMessagesToDrop = 1; |
| GetLoopback().mNumMessagesToAllowBeforeDropping = 1; |
| EXPECT_EQ(commandSender.SendCommandRequest(GetSessionBobToAlice()), CHIP_NO_ERROR); |
| DrainAndServiceIO(); |
| |
| EXPECT_EQ(GetLoopback().mSentMessageCount, 2u); |
| EXPECT_EQ(GetLoopback().mDroppedMessageCount, 1u); |
| |
| EXPECT_EQ(mockCommandSenderDelegate.onResponseCalledTimes, 0); |
| EXPECT_EQ(mockCommandSenderDelegate.onFinalCalledTimes, 0); |
| EXPECT_EQ(mockCommandSenderDelegate.onErrorCalledTimes, 0); |
| |
| EXPECT_EQ(GetNumActiveCommandResponderObjects(), 0u); |
| |
| System::PacketBufferHandle msgBuf = System::PacketBufferHandle::New(kMaxSecureSduLengthBytes); |
| ASSERT_FALSE(msgBuf.IsNull()); |
| System::PacketBufferTLVWriter writer; |
| writer.Init(std::move(msgBuf)); |
| ReportDataMessage::Builder response; |
| response.Init(&writer); |
| EXPECT_EQ(writer.Finalize(&msgBuf), CHIP_NO_ERROR); |
| |
| PayloadHeader payloadHeader; |
| payloadHeader.SetExchangeID(0); |
| payloadHeader.SetMessageType(chip::Protocols::InteractionModel::MsgType::ReportData); |
| chip::Test::MessageCapturer messageLog(*this); |
| messageLog.mCaptureStandaloneAcks = false; |
| |
| // Since we are dropping packets, things are not getting acked. Set up our |
| // MRP state to look like what it would have looked like if the packet had |
| // not gotten dropped. |
| |
| PretendWeGotReplyFromServer(*this, commandSender.mExchangeCtx.Get()); |
| |
| GetLoopback().mSentMessageCount = 0; |
| GetLoopback().mNumMessagesToDrop = 0; |
| GetLoopback().mNumMessagesToAllowBeforeDropping = 0; |
| GetLoopback().mDroppedMessageCount = 0; |
| |
| EXPECT_EQ(commandSender.OnMessageReceived(commandSender.mExchangeCtx.Get(), payloadHeader, std::move(msgBuf)), |
| CHIP_ERROR_INVALID_MESSAGE_TYPE); |
| EXPECT_EQ(mockCommandSenderDelegate.mError, CHIP_ERROR_INVALID_MESSAGE_TYPE); |
| EXPECT_EQ(mockCommandSenderDelegate.onResponseCalledTimes, 0); |
| EXPECT_EQ(mockCommandSenderDelegate.onFinalCalledTimes, 1); |
| EXPECT_EQ(mockCommandSenderDelegate.onErrorCalledTimes, 1); |
| |
| DrainAndServiceIO(); |
| |
| // Client sent status report with invalid action, server's exchange has been closed, so all it sent is an MRP Ack |
| EXPECT_EQ(GetLoopback().mSentMessageCount, 2u); |
| CheckForInvalidAction(messageLog); |
| EXPECT_EQ(GetNumActiveCommandResponderObjects(), 0u); |
| ExpireSessionAliceToBob(); |
| ExpireSessionBobToAlice(); |
| CreateSessionAliceToBob(); |
| CreateSessionBobToAlice(); |
| } |
| |
| // Command Sender sends invoke request, command handler drops invoke response, then test injects malformed invoke response |
| // message to client, client sends out status response with invalid action. |
| TEST_F_FROM_FIXTURE(TestCommandInteraction, TestCommandInvalidMessage3) |
| { |
| mockCommandSenderDelegate.ResetCounter(); |
| app::CommandSender commandSender(&mockCommandSenderDelegate, &GetExchangeManager()); |
| |
| AddInvokeRequestData(&commandSender); |
| asyncCommand = false; |
| |
| GetLoopback().mSentMessageCount = 0; |
| GetLoopback().mNumMessagesToDrop = 1; |
| GetLoopback().mNumMessagesToAllowBeforeDropping = 1; |
| EXPECT_EQ(commandSender.SendCommandRequest(GetSessionBobToAlice()), CHIP_NO_ERROR); |
| DrainAndServiceIO(); |
| |
| EXPECT_EQ(GetLoopback().mSentMessageCount, 2u); |
| EXPECT_EQ(GetLoopback().mDroppedMessageCount, 1u); |
| |
| EXPECT_EQ(mockCommandSenderDelegate.onResponseCalledTimes, 0); |
| EXPECT_EQ(mockCommandSenderDelegate.onFinalCalledTimes, 0); |
| EXPECT_EQ(mockCommandSenderDelegate.onErrorCalledTimes, 0); |
| |
| EXPECT_EQ(GetNumActiveCommandResponderObjects(), 0u); |
| |
| System::PacketBufferHandle msgBuf = System::PacketBufferHandle::New(kMaxSecureSduLengthBytes); |
| ASSERT_FALSE(msgBuf.IsNull()); |
| System::PacketBufferTLVWriter writer; |
| writer.Init(std::move(msgBuf)); |
| InvokeResponseMessage::Builder response; |
| response.Init(&writer); |
| EXPECT_EQ(writer.Finalize(&msgBuf), CHIP_NO_ERROR); |
| |
| PayloadHeader payloadHeader; |
| payloadHeader.SetExchangeID(0); |
| payloadHeader.SetMessageType(chip::Protocols::InteractionModel::MsgType::InvokeCommandResponse); |
| chip::Test::MessageCapturer messageLog(*this); |
| messageLog.mCaptureStandaloneAcks = false; |
| |
| // Since we are dropping packets, things are not getting acked. Set up our |
| // MRP state to look like what it would have looked like if the packet had |
| // not gotten dropped. |
| PretendWeGotReplyFromServer(*this, commandSender.mExchangeCtx.Get()); |
| |
| GetLoopback().mSentMessageCount = 0; |
| GetLoopback().mNumMessagesToDrop = 0; |
| GetLoopback().mNumMessagesToAllowBeforeDropping = 0; |
| GetLoopback().mDroppedMessageCount = 0; |
| |
| EXPECT_EQ(commandSender.OnMessageReceived(commandSender.mExchangeCtx.Get(), payloadHeader, std::move(msgBuf)), |
| CHIP_ERROR_END_OF_TLV); |
| EXPECT_EQ(mockCommandSenderDelegate.mError, CHIP_ERROR_END_OF_TLV); |
| EXPECT_EQ(mockCommandSenderDelegate.onResponseCalledTimes, 0); |
| EXPECT_EQ(mockCommandSenderDelegate.onFinalCalledTimes, 1); |
| EXPECT_EQ(mockCommandSenderDelegate.onErrorCalledTimes, 1); |
| |
| DrainAndServiceIO(); |
| |
| // Client sent status report with invalid action, server's exchange has been closed, so all it sent is an MRP Ack |
| EXPECT_EQ(GetLoopback().mSentMessageCount, 2u); |
| CheckForInvalidAction(messageLog); |
| EXPECT_EQ(GetNumActiveCommandResponderObjects(), 0u); |
| ExpireSessionAliceToBob(); |
| ExpireSessionBobToAlice(); |
| CreateSessionAliceToBob(); |
| CreateSessionBobToAlice(); |
| } |
| |
| // Command Sender sends invoke request, command handler drops invoke response, then test injects malformed status response to |
| // client, client responds to the status response with invalid action. |
| TEST_F_FROM_FIXTURE(TestCommandInteraction, TestCommandInvalidMessage4) |
| { |
| mockCommandSenderDelegate.ResetCounter(); |
| app::CommandSender commandSender(&mockCommandSenderDelegate, &GetExchangeManager()); |
| |
| AddInvokeRequestData(&commandSender); |
| asyncCommand = false; |
| |
| GetLoopback().mSentMessageCount = 0; |
| GetLoopback().mNumMessagesToDrop = 1; |
| GetLoopback().mNumMessagesToAllowBeforeDropping = 1; |
| EXPECT_EQ(commandSender.SendCommandRequest(GetSessionBobToAlice()), CHIP_NO_ERROR); |
| DrainAndServiceIO(); |
| |
| EXPECT_EQ(GetLoopback().mSentMessageCount, 2u); |
| EXPECT_EQ(GetLoopback().mDroppedMessageCount, 1u); |
| |
| EXPECT_EQ(mockCommandSenderDelegate.onResponseCalledTimes, 0); |
| EXPECT_EQ(mockCommandSenderDelegate.onFinalCalledTimes, 0); |
| EXPECT_EQ(mockCommandSenderDelegate.onErrorCalledTimes, 0); |
| EXPECT_EQ(GetNumActiveCommandResponderObjects(), 0u); |
| |
| System::PacketBufferHandle msgBuf = System::PacketBufferHandle::New(kMaxSecureSduLengthBytes); |
| ASSERT_FALSE(msgBuf.IsNull()); |
| System::PacketBufferTLVWriter writer; |
| writer.Init(std::move(msgBuf)); |
| StatusResponseMessage::Builder response; |
| response.Init(&writer); |
| EXPECT_EQ(writer.Finalize(&msgBuf), CHIP_NO_ERROR); |
| |
| PayloadHeader payloadHeader; |
| payloadHeader.SetExchangeID(0); |
| payloadHeader.SetMessageType(chip::Protocols::InteractionModel::MsgType::StatusResponse); |
| chip::Test::MessageCapturer messageLog(*this); |
| messageLog.mCaptureStandaloneAcks = false; |
| |
| // Since we are dropping packets, things are not getting acked. Set up our |
| // MRP state to look like what it would have looked like if the packet had |
| // not gotten dropped. |
| PretendWeGotReplyFromServer(*this, commandSender.mExchangeCtx.Get()); |
| |
| GetLoopback().mSentMessageCount = 0; |
| GetLoopback().mNumMessagesToDrop = 0; |
| GetLoopback().mNumMessagesToAllowBeforeDropping = 0; |
| GetLoopback().mDroppedMessageCount = 0; |
| |
| EXPECT_EQ(commandSender.OnMessageReceived(commandSender.mExchangeCtx.Get(), payloadHeader, std::move(msgBuf)), |
| CHIP_ERROR_END_OF_TLV); |
| EXPECT_EQ(mockCommandSenderDelegate.mError, CHIP_ERROR_END_OF_TLV); |
| EXPECT_EQ(mockCommandSenderDelegate.onResponseCalledTimes, 0); |
| EXPECT_EQ(mockCommandSenderDelegate.onFinalCalledTimes, 1); |
| EXPECT_EQ(mockCommandSenderDelegate.onErrorCalledTimes, 1); |
| |
| DrainAndServiceIO(); |
| |
| // Client sent status report with invalid action, server's exchange has been closed, so all it sent is an MRP Ack |
| EXPECT_EQ(GetLoopback().mSentMessageCount, 2u); |
| CheckForInvalidAction(messageLog); |
| EXPECT_EQ(GetNumActiveCommandResponderObjects(), 0u); |
| ExpireSessionAliceToBob(); |
| ExpireSessionBobToAlice(); |
| CreateSessionAliceToBob(); |
| CreateSessionBobToAlice(); |
| } |
| |
| TEST_F(TestCommandInteraction, TestCommandSender_WithWrongState) |
| { |
| |
| app::CommandSender commandSender(&mockCommandSenderDelegate, &GetExchangeManager()); |
| |
| EXPECT_EQ(commandSender.SendCommandRequest(GetSessionBobToAlice()), CHIP_ERROR_INCORRECT_STATE); |
| } |
| |
| TEST_F(TestCommandInteraction, TestCommandHandler_WithWrongState) |
| { |
| ConcreteCommandPath requestCommandPath = { kTestEndpointId, kTestClusterId, kTestCommandIdNoData }; |
| ConcreteCommandPath responseCommandPath = { kTestEndpointId, kTestClusterId, kTestCommandIdNoData }; |
| |
| CommandHandlerWithUnrespondedCommand commandHandler(&mockCommandHandlerDelegate, requestCommandPath, |
| /* aRef = */ NullOptional); |
| |
| { |
| // This simulates how cluster would call CommandHandler APIs synchronously. There would |
| // be handle already acquired on the callers behalf. |
| CommandHandler::Handle handle(&commandHandler); |
| |
| const CommandHandlerImpl::InvokeResponseParameters prepareParams(requestCommandPath); |
| EXPECT_EQ(commandHandler.PrepareInvokeResponseCommand(responseCommandPath, prepareParams), CHIP_NO_ERROR); |
| } |
| EXPECT_TRUE(commandHandler.mMockCommandResponder.mChunks.IsNull()); |
| } |
| |
| TEST_F_FROM_FIXTURE(TestCommandInteraction, TestCommandSender_WithSendCommand) |
| { |
| |
| app::CommandSender commandSender(&mockCommandSenderDelegate, &GetExchangeManager()); |
| |
| System::PacketBufferHandle buf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize); |
| |
| AddInvokeRequestData(&commandSender); |
| EXPECT_EQ(commandSender.SendCommandRequest(GetSessionBobToAlice()), CHIP_NO_ERROR); |
| |
| DrainAndServiceIO(); |
| |
| GenerateInvokeResponse(buf, kTestCommandIdWithData); |
| bool moreChunkedMessages = false; |
| EXPECT_EQ(commandSender.ProcessInvokeResponse(std::move(buf), moreChunkedMessages), CHIP_NO_ERROR); |
| EXPECT_FALSE(moreChunkedMessages); |
| } |
| |
| TEST_F(TestCommandInteraction, TestCommandHandler_WithSendEmptyCommand) |
| { |
| ConcreteCommandPath requestCommandPath = { kTestEndpointId, kTestClusterId, kTestCommandIdNoData }; |
| ConcreteCommandPath responseCommandPath = { kTestEndpointId, kTestClusterId, kTestCommandIdNoData }; |
| |
| CommandHandlerWithUnrespondedCommand commandHandler(&mockCommandHandlerDelegate, requestCommandPath, |
| /* aRef = */ NullOptional); |
| |
| { |
| // This simulates how cluster would call CommandHandler APIs synchronously. There would |
| // be handle already acquired on the callers behalf. |
| CommandHandler::Handle handle(&commandHandler); |
| |
| const CommandHandlerImpl::InvokeResponseParameters prepareParams(requestCommandPath); |
| EXPECT_EQ(commandHandler.PrepareInvokeResponseCommand(responseCommandPath, prepareParams), CHIP_NO_ERROR); |
| EXPECT_EQ(commandHandler.FinishCommand(), CHIP_NO_ERROR); |
| } |
| EXPECT_FALSE(commandHandler.mMockCommandResponder.mChunks.IsNull()); |
| } |
| |
| TEST_F_FROM_FIXTURE(TestCommandInteraction, TestCommandSender_WithProcessReceivedMsg) |
| { |
| |
| app::CommandSender commandSender(&mockCommandSenderDelegate, &GetExchangeManager()); |
| |
| System::PacketBufferHandle buf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize); |
| |
| GenerateInvokeResponse(buf, kTestCommandIdWithData); |
| bool moreChunkedMessages = false; |
| |
| EXPECT_EQ(commandSender.ProcessInvokeResponse(std::move(buf), moreChunkedMessages), CHIP_NO_ERROR); |
| EXPECT_FALSE(moreChunkedMessages); |
| } |
| |
| TEST_F_FROM_FIXTURE(TestCommandInteraction, TestCommandSender_ExtendableApiWithProcessReceivedMsg) |
| { |
| |
| mockCommandSenderExtendedDelegate.ResetCounter(); |
| PendingResponseTrackerImpl pendingResponseTracker; |
| app::CommandSender commandSender(kCommandSenderTestOnlyMarker, &mockCommandSenderExtendedDelegate, &GetExchangeManager(), |
| &pendingResponseTracker); |
| |
| uint16_t mockCommandRef = 1; |
| pendingResponseTracker.Add(mockCommandRef); |
| commandSender.mFinishedCommandCount = 1; |
| |
| System::PacketBufferHandle buf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize); |
| |
| GenerateInvokeResponse(buf, kTestCommandIdWithData); |
| bool moreChunkedMessages = false; |
| EXPECT_EQ(commandSender.ProcessInvokeResponse(std::move(buf), moreChunkedMessages), CHIP_NO_ERROR); |
| EXPECT_FALSE(moreChunkedMessages); |
| |
| commandSender.FlushNoCommandResponse(); |
| |
| EXPECT_EQ(mockCommandSenderExtendedDelegate.onResponseCalledTimes, 1); |
| EXPECT_EQ(mockCommandSenderExtendedDelegate.onFinalCalledTimes, 0); |
| EXPECT_EQ(mockCommandSenderExtendedDelegate.onNoResponseCalledTimes, 0); |
| EXPECT_EQ(mockCommandSenderExtendedDelegate.onErrorCalledTimes, 0); |
| } |
| |
| TEST_F_FROM_FIXTURE(TestCommandInteraction, TestCommandSender_ExtendableApiWithProcessReceivedMsgContainingInvalidCommandRef) |
| { |
| |
| mockCommandSenderExtendedDelegate.ResetCounter(); |
| PendingResponseTrackerImpl pendingResponseTracker; |
| app::CommandSender commandSender(kCommandSenderTestOnlyMarker, &mockCommandSenderExtendedDelegate, &GetExchangeManager(), |
| &pendingResponseTracker); |
| |
| uint16_t mockCommandRef = 1; |
| pendingResponseTracker.Add(mockCommandRef); |
| |
| commandSender.mFinishedCommandCount = 1; |
| |
| System::PacketBufferHandle buf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize); |
| |
| uint16_t invalidResponseCommandRef = 2; |
| GenerateInvokeResponse(buf, kTestCommandIdWithData, kTestClusterId, kTestEndpointId, |
| std::make_optional(invalidResponseCommandRef)); |
| bool moreChunkedMessages = false; |
| EXPECT_EQ(commandSender.ProcessInvokeResponse(std::move(buf), moreChunkedMessages), CHIP_ERROR_KEY_NOT_FOUND); |
| EXPECT_FALSE(moreChunkedMessages); |
| |
| commandSender.FlushNoCommandResponse(); |
| |
| EXPECT_EQ(mockCommandSenderExtendedDelegate.onResponseCalledTimes, 0); |
| EXPECT_EQ(mockCommandSenderExtendedDelegate.onFinalCalledTimes, 0); |
| EXPECT_EQ(mockCommandSenderExtendedDelegate.onNoResponseCalledTimes, 1); |
| EXPECT_EQ(mockCommandSenderExtendedDelegate.onErrorCalledTimes, 0); |
| } |
| |
| TEST_F_FROM_FIXTURE(TestCommandInteraction, TestCommandSender_ValidateSecondLargeAddRequestDataRollbacked) |
| { |
| mockCommandSenderExtendedDelegate.ResetCounter(); |
| PendingResponseTrackerImpl pendingResponseTracker; |
| app::CommandSender commandSender(kCommandSenderTestOnlyMarker, &mockCommandSenderExtendedDelegate, &GetExchangeManager(), |
| &pendingResponseTracker); |
| |
| app::CommandSender::AddRequestDataParameters addRequestDataParams; |
| |
| CommandSender::ConfigParameters config; |
| config.SetRemoteMaxPathsPerInvoke(2); |
| EXPECT_EQ(commandSender.SetCommandSenderConfig(config), CHIP_NO_ERROR); |
| |
| // The specific values chosen here are arbitrary. |
| uint16_t firstCommandRef = 1; |
| uint16_t secondCommandRef = 2; |
| auto commandPathParams = MakeTestCommandPath(); |
| SimpleTLVPayload simplePayloadWriter; |
| addRequestDataParams.SetCommandRef(firstCommandRef); |
| |
| EXPECT_EQ(commandSender.AddRequestData(commandPathParams, simplePayloadWriter, addRequestDataParams), CHIP_NO_ERROR); |
| |
| uint32_t remainingSize = commandSender.mInvokeRequestBuilder.GetWriter()->GetRemainingFreeLength(); |
| // Because request is made of both request data and request path (commandPathParams), using |
| // `remainingSize` is large enough fail. |
| ForcedSizeBuffer requestData(remainingSize); |
| |
| addRequestDataParams.SetCommandRef(secondCommandRef); |
| EXPECT_EQ(commandSender.AddRequestData(commandPathParams, requestData, addRequestDataParams), CHIP_ERROR_NO_MEMORY); |
| |
| // Confirm that we can still send out a request with the first command. |
| EXPECT_EQ(commandSender.SendCommandRequest(GetSessionBobToAlice()), CHIP_NO_ERROR); |
| EXPECT_EQ(commandSender.GetInvokeResponseMessageCount(), 0u); |
| |
| DrainAndServiceIO(); |
| |
| EXPECT_EQ(mockCommandSenderExtendedDelegate.onResponseCalledTimes, 1); |
| EXPECT_EQ(mockCommandSenderExtendedDelegate.onFinalCalledTimes, 1); |
| EXPECT_EQ(mockCommandSenderExtendedDelegate.onErrorCalledTimes, 0); |
| } |
| |
| TEST_F(TestCommandInteraction, TestCommandHandlerEncodeSimpleCommandData) |
| { |
| // Send response which has simple command data and command path |
| ValidateCommandHandlerEncodeInvokeResponseMessage(false /*aNeedStatusCode=false*/); |
| } |
| |
| TEST_F(TestCommandInteraction, TestCommandHandlerCommandDataEncoding) |
| { |
| auto path = MakeTestCommandPath(); |
| ConcreteCommandPath requestCommandPath(path.mEndpointId, path.mClusterId, path.mCommandId); |
| CommandHandlerWithUnrespondedCommand commandHandler(nullptr, requestCommandPath, /* aRef = */ NullOptional); |
| |
| { |
| // This simulates how cluster would call CommandHandler APIs synchronously. There would |
| // be handle already acquired on the callers behalf. |
| CommandHandler::Handle handle(&commandHandler); |
| |
| commandHandler.AddResponse(requestCommandPath, Fields()); |
| } |
| EXPECT_FALSE(commandHandler.mMockCommandResponder.mChunks.IsNull()); |
| } |
| |
| TEST_F(TestCommandInteraction, TestCommandHandlerCommandEncodeFailure) |
| { |
| auto path = MakeTestCommandPath(); |
| auto requestCommandPath = ConcreteCommandPath(path.mEndpointId, path.mClusterId, path.mCommandId); |
| CommandHandlerWithUnrespondedCommand commandHandler(nullptr, requestCommandPath, NullOptional); |
| |
| { |
| // This simulates how cluster would call CommandHandler APIs synchronously. There would |
| // be handle already acquired on the callers behalf. |
| CommandHandler::Handle handle(&commandHandler); |
| |
| commandHandler.AddResponse(requestCommandPath, BadFields()); |
| } |
| EXPECT_FALSE(commandHandler.mMockCommandResponder.mChunks.IsNull()); |
| } |
| |
| // Command Sender sends malformed invoke request, handler fails to process it and sends status report with invalid action |
| TEST_F(TestCommandInteraction, TestCommandHandlerInvalidMessageSync) |
| { |
| |
| mockCommandSenderDelegate.ResetCounter(); |
| app::CommandSender commandSender(&mockCommandSenderDelegate, &GetExchangeManager()); |
| |
| chip::isCommandDispatched = false; |
| AddInvalidInvokeRequestData(&commandSender); |
| EXPECT_EQ(commandSender.SendCommandRequest(GetSessionBobToAlice()), CHIP_NO_ERROR); |
| |
| DrainAndServiceIO(); |
| |
| EXPECT_FALSE(chip::isCommandDispatched); |
| EXPECT_EQ(mockCommandSenderDelegate.onResponseCalledTimes, 0); |
| EXPECT_EQ(mockCommandSenderDelegate.onFinalCalledTimes, 1); |
| EXPECT_EQ(mockCommandSenderDelegate.onErrorCalledTimes, 1); |
| EXPECT_EQ(mockCommandSenderDelegate.mError, CHIP_IM_GLOBAL_STATUS(InvalidAction)); |
| EXPECT_EQ(GetNumActiveCommandResponderObjects(), 0u); |
| EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); |
| } |
| |
| TEST_F(TestCommandInteraction, TestCommandHandlerCommandEncodeExternalFailure) |
| { |
| auto path = MakeTestCommandPath(); |
| auto requestCommandPath = ConcreteCommandPath(path.mEndpointId, path.mClusterId, path.mCommandId); |
| CommandHandlerWithUnrespondedCommand commandHandler(nullptr, requestCommandPath, NullOptional); |
| |
| { |
| // This simulates how cluster would call CommandHandler APIs synchronously. There would |
| // be handle already acquired on the callers behalf. |
| CommandHandler::Handle handle(&commandHandler); |
| |
| EXPECT_NE(commandHandler.AddResponseData(requestCommandPath, BadFields()), CHIP_NO_ERROR); |
| commandHandler.AddStatus(requestCommandPath, Protocols::InteractionModel::Status::Failure); |
| } |
| EXPECT_FALSE(commandHandler.mMockCommandResponder.mChunks.IsNull()); |
| } |
| |
| TEST_F(TestCommandInteraction, TestCommandHandlerEncodeSimpleStatusCode) |
| { |
| // Send response which has simple status code and command path |
| ValidateCommandHandlerEncodeInvokeResponseMessage(true /*aNeedStatusCode=true*/); |
| } |
| |
| TEST_F_FROM_FIXTURE(TestCommandInteraction, TestCommandHandler_WithoutResponderCallingAddStatus) |
| { |
| chip::app::ConcreteCommandPath requestCommandPath(kTestEndpointId, kTestClusterId, kTestCommandIdWithData); |
| CommandHandlerImpl commandHandler(&mockCommandHandlerDelegate); |
| |
| commandHandler.AddStatus(requestCommandPath, Protocols::InteractionModel::Status::Failure); |
| |
| // Since calling AddStatus is supposed to be a no-operation when there is no responder, it is |
| // hard to validate. Best way is to check that we are still in an Idle state afterwards |
| EXPECT_TRUE(commandHandler.TestOnlyIsInIdleState()); |
| } |
| |
| TEST_F_FROM_FIXTURE(TestCommandInteraction, TestCommandHandler_WithoutResponderCallingAddResponse) |
| { |
| chip::app::ConcreteCommandPath requestCommandPath(kTestEndpointId, kTestClusterId, kTestCommandIdWithData); |
| CommandHandlerImpl commandHandler(&mockCommandHandlerDelegate); |
| |
| uint32_t sizeToFill = 50; // This is an arbitrary number, we need to select a non-zero value. |
| ForcedSizeBuffer responseData(sizeToFill); |
| EXPECT_EQ(commandHandler.AddResponseData(requestCommandPath, responseData.GetCommandId(), responseData), CHIP_NO_ERROR); |
| |
| // Since calling AddResponseData is supposed to be a no-operation when there is no responder, it is |
| // hard to validate. Best way is to check that we are still in an Idle state afterwards |
| EXPECT_TRUE(commandHandler.TestOnlyIsInIdleState()); |
| } |
| |
| TEST_F_FROM_FIXTURE(TestCommandInteraction, TestCommandHandler_WithoutResponderCallingDirectPrepareFinishCommandApis) |
| { |
| chip::app::ConcreteCommandPath requestCommandPath(kTestEndpointId, kTestClusterId, kTestCommandIdWithData); |
| CommandHandlerImpl commandHandler(&mockCommandHandlerDelegate); |
| |
| // We intentionally prevent successful calls to PrepareInvokeResponseCommand and FinishCommand when no |
| // responder is present. This aligns with the design decision to promote AddStatus and AddResponseData |
| // usage in such scenarios. See GitHub issue #32486 for discussions on phasing out external use of |
| // these primitives. |
| const CommandHandlerImpl::InvokeResponseParameters prepareParams(requestCommandPath); |
| ConcreteCommandPath responseCommandPath = { kTestEndpointId, kTestClusterId, kTestCommandIdCommandSpecificResponse }; |
| EXPECT_EQ(commandHandler.PrepareInvokeResponseCommand(responseCommandPath, prepareParams), CHIP_ERROR_INCORRECT_STATE); |
| |
| EXPECT_EQ(commandHandler.GetCommandDataIBTLVWriter(), nullptr); |
| |
| EXPECT_EQ(commandHandler.FinishCommand(), CHIP_ERROR_INCORRECT_STATE); |
| |
| EXPECT_TRUE(commandHandler.TestOnlyIsInIdleState()); |
| } |
| |
| TEST_F(TestCommandInteraction, TestCommandHandler_WithOnInvokeReceivedNotExistCommand) |
| { |
| System::PacketBufferHandle commandDatabuf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize); |
| // Use some invalid endpoint / cluster / command. |
| GenerateInvokeRequest(commandDatabuf, /* aIsTimedRequest = */ false, 0xEF /* command */, 0xADBE /* cluster */, |
| 0xDE /* endpoint */); |
| CommandHandlerImpl commandHandler(&mockCommandHandlerDelegate); |
| chip::isCommandDispatched = false; |
| |
| mockCommandHandlerDelegate.ResetCounter(); |
| MockCommandResponder mockCommandResponder; |
| Protocols::InteractionModel::Status status = |
| commandHandler.OnInvokeCommandRequest(mockCommandResponder, std::move(commandDatabuf), false); |
| |
| EXPECT_EQ(status, Protocols::InteractionModel::Status::InvalidAction); |
| EXPECT_TRUE(mockCommandResponder.mChunks.IsNull()); |
| // TODO we can further validate the response is what we expected. |
| EXPECT_FALSE(chip::isCommandDispatched); |
| } |
| |
| TEST_F(TestCommandInteraction, TestCommandHandler_WithOnInvokeReceivedEmptyDataMsg) |
| { |
| bool allBooleans[] = { true, false }; |
| for (auto messageIsTimed : allBooleans) |
| { |
| for (auto transactionIsTimed : allBooleans) |
| { |
| mockCommandHandlerDelegate.ResetCounter(); |
| CommandHandlerImpl commandHandler(&mockCommandHandlerDelegate); |
| System::PacketBufferHandle commandDatabuf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize); |
| |
| chip::isCommandDispatched = false; |
| GenerateInvokeRequest(commandDatabuf, messageIsTimed, kTestCommandIdNoData); |
| MockCommandResponder mockCommandResponder; |
| Protocols::InteractionModel::Status status = |
| commandHandler.OnInvokeCommandRequest(mockCommandResponder, std::move(commandDatabuf), transactionIsTimed); |
| |
| if (messageIsTimed != transactionIsTimed) |
| { |
| EXPECT_EQ(status, Protocols::InteractionModel::Status::TimedRequestMismatch); |
| EXPECT_TRUE(mockCommandResponder.mChunks.IsNull()); |
| } |
| else |
| { |
| EXPECT_EQ(status, Protocols::InteractionModel::Status::Success); |
| EXPECT_FALSE(mockCommandResponder.mChunks.IsNull()); |
| } |
| EXPECT_TRUE(chip::isCommandDispatched == (messageIsTimed == transactionIsTimed)); |
| } |
| } |
| } |
| |
| TEST_F(TestCommandInteraction, TestCommandSenderLegacyCallbackUnsupportedCommand) |
| { |
| |
| mockCommandSenderDelegate.ResetCounter(); |
| app::CommandSender commandSender(&mockCommandSenderDelegate, &GetExchangeManager()); |
| |
| AddInvokeRequestData(&commandSender, kTestNonExistCommandId); |
| EXPECT_EQ(commandSender.SendCommandRequest(GetSessionBobToAlice()), CHIP_NO_ERROR); |
| |
| DrainAndServiceIO(); |
| |
| EXPECT_EQ(mockCommandSenderDelegate.onResponseCalledTimes, 0); |
| EXPECT_EQ(mockCommandSenderDelegate.onFinalCalledTimes, 1); |
| EXPECT_EQ(mockCommandSenderDelegate.onErrorCalledTimes, 1); |
| EXPECT_EQ(GetNumActiveCommandResponderObjects(), 0u); |
| EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); |
| } |
| |
| // Because UnsupportedCommand is a path specific error we will expect it to come via on response when using Extended Path. |
| TEST_F(TestCommandInteraction, TestCommandSender_ExtendableCallbackUnsupportedCommand) |
| { |
| |
| mockCommandSenderExtendedDelegate.ResetCounter(); |
| app::CommandSender commandSender(&mockCommandSenderExtendedDelegate, &GetExchangeManager()); |
| |
| AddInvokeRequestData(&commandSender, kTestNonExistCommandId); |
| EXPECT_EQ(commandSender.SendCommandRequest(GetSessionBobToAlice()), CHIP_NO_ERROR); |
| |
| DrainAndServiceIO(); |
| |
| EXPECT_EQ(mockCommandSenderExtendedDelegate.onResponseCalledTimes, 1); |
| EXPECT_EQ(mockCommandSenderExtendedDelegate.onFinalCalledTimes, 1); |
| EXPECT_EQ(mockCommandSenderExtendedDelegate.onErrorCalledTimes, 0); |
| |
| EXPECT_EQ(GetNumActiveCommandResponderObjects(), 0u); |
| EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); |
| } |
| |
| TEST_F(TestCommandInteraction, TestCommandSenderLegacyCallbackBuildingBatchCommandFails) |
| { |
| mockCommandSenderDelegate.ResetCounter(); |
| app::CommandSender commandSender(&mockCommandSenderDelegate, &GetExchangeManager()); |
| app::CommandSender::PrepareCommandParameters prepareCommandParams; |
| app::CommandSender::FinishCommandParameters finishCommandParams; |
| prepareCommandParams.SetStartDataStruct(true).SetCommandRef(0); |
| finishCommandParams.SetEndDataStruct(true).SetCommandRef(0); |
| |
| CommandSender::ConfigParameters config; |
| config.SetRemoteMaxPathsPerInvoke(2); |
| EXPECT_EQ(commandSender.SetCommandSenderConfig(config), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| // Even though we got an error saying invalid argument we are going to attempt |
| // to add two commands. |
| |
| auto commandPathParams = MakeTestCommandPath(); |
| EXPECT_EQ(commandSender.PrepareCommand(commandPathParams, prepareCommandParams), CHIP_NO_ERROR); |
| chip::TLV::TLVWriter * writer = commandSender.GetCommandDataIBTLVWriter(); |
| EXPECT_EQ(writer->PutBoolean(chip::TLV::ContextTag(1), true), CHIP_NO_ERROR); |
| EXPECT_EQ(commandSender.FinishCommand(finishCommandParams), CHIP_NO_ERROR); |
| // Preparing second command. |
| prepareCommandParams.SetCommandRef(1); |
| EXPECT_EQ(commandSender.PrepareCommand(commandPathParams, prepareCommandParams), CHIP_ERROR_INCORRECT_STATE); |
| |
| EXPECT_EQ(GetNumActiveCommandResponderObjects(), 0u); |
| EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); |
| } |
| |
| TEST_F(TestCommandInteraction, TestCommandSender_ExtendableCallbackBuildingBatchDuplicateCommandRefFails) |
| { |
| mockCommandSenderExtendedDelegate.ResetCounter(); |
| PendingResponseTrackerImpl pendingResponseTracker; |
| app::CommandSender commandSender(kCommandSenderTestOnlyMarker, &mockCommandSenderExtendedDelegate, &GetExchangeManager(), |
| &pendingResponseTracker); |
| app::CommandSender::PrepareCommandParameters prepareCommandParams; |
| app::CommandSender::FinishCommandParameters finishCommandParams; |
| |
| CommandSender::ConfigParameters config; |
| config.SetRemoteMaxPathsPerInvoke(2); |
| EXPECT_EQ(commandSender.SetCommandSenderConfig(config), CHIP_NO_ERROR); |
| |
| prepareCommandParams.SetStartDataStruct(true).SetCommandRef(0); |
| finishCommandParams.SetEndDataStruct(true).SetCommandRef(0); |
| auto commandPathParams = MakeTestCommandPath(); |
| EXPECT_EQ(commandSender.PrepareCommand(commandPathParams, prepareCommandParams), CHIP_NO_ERROR); |
| chip::TLV::TLVWriter * writer = commandSender.GetCommandDataIBTLVWriter(); |
| EXPECT_EQ(writer->PutBoolean(chip::TLV::ContextTag(1), true), CHIP_NO_ERROR); |
| EXPECT_EQ(commandSender.FinishCommand(finishCommandParams), CHIP_NO_ERROR); |
| // Preparing second command. |
| prepareCommandParams.SetCommandRef(0); |
| EXPECT_EQ(commandSender.PrepareCommand(commandPathParams, prepareCommandParams), CHIP_ERROR_INVALID_ARGUMENT); |
| |
| EXPECT_EQ(GetNumActiveCommandResponderObjects(), 0u); |
| EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); |
| } |
| |
| TEST_F(TestCommandInteraction, TestCommandSender_ExtendableCallbackBuildingBatchCommandSuccess) |
| { |
| mockCommandSenderExtendedDelegate.ResetCounter(); |
| PendingResponseTrackerImpl pendingResponseTracker; |
| app::CommandSender commandSender(kCommandSenderTestOnlyMarker, &mockCommandSenderExtendedDelegate, &GetExchangeManager(), |
| &pendingResponseTracker); |
| app::CommandSender::PrepareCommandParameters prepareCommandParams; |
| app::CommandSender::FinishCommandParameters finishCommandParams; |
| |
| CommandSender::ConfigParameters config; |
| config.SetRemoteMaxPathsPerInvoke(2); |
| EXPECT_EQ(commandSender.SetCommandSenderConfig(config), CHIP_NO_ERROR); |
| |
| // The specific values chosen here are arbitrary. This test primarily verifies that we can |
| // use a larger command reference value followed by a smaller one for subsequent command. |
| uint16_t firstCommandRef = 40; |
| uint16_t secondCommandRef = 2; |
| prepareCommandParams.SetStartDataStruct(true).SetCommandRef(firstCommandRef); |
| finishCommandParams.SetEndDataStruct(true).SetCommandRef(firstCommandRef); |
| auto commandPathParams = MakeTestCommandPath(); |
| EXPECT_EQ(commandSender.PrepareCommand(commandPathParams, prepareCommandParams), CHIP_NO_ERROR); |
| chip::TLV::TLVWriter * writer = commandSender.GetCommandDataIBTLVWriter(); |
| EXPECT_EQ(writer->PutBoolean(chip::TLV::ContextTag(1), true), CHIP_NO_ERROR); |
| EXPECT_EQ(commandSender.FinishCommand(finishCommandParams), CHIP_NO_ERROR); |
| // Preparing second command. |
| prepareCommandParams.SetCommandRef(secondCommandRef); |
| finishCommandParams.SetCommandRef(secondCommandRef); |
| EXPECT_EQ(commandSender.PrepareCommand(commandPathParams, prepareCommandParams), CHIP_NO_ERROR); |
| writer = commandSender.GetCommandDataIBTLVWriter(); |
| EXPECT_EQ(writer->PutBoolean(chip::TLV::ContextTag(1), true), CHIP_NO_ERROR); |
| EXPECT_EQ(commandSender.FinishCommand(finishCommandParams), CHIP_NO_ERROR); |
| |
| EXPECT_EQ(GetNumActiveCommandResponderObjects(), 0u); |
| EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); |
| } |
| |
| TEST_F(TestCommandInteraction, TestCommandSenderCommandSuccessResponseFlow) |
| { |
| |
| mockCommandSenderDelegate.ResetCounter(); |
| app::CommandSender commandSender(&mockCommandSenderDelegate, &GetExchangeManager()); |
| |
| AddInvokeRequestData(&commandSender); |
| EXPECT_EQ(commandSender.SendCommandRequest(GetSessionBobToAlice()), CHIP_NO_ERROR); |
| EXPECT_EQ(commandSender.GetInvokeResponseMessageCount(), 0u); |
| |
| DrainAndServiceIO(); |
| |
| EXPECT_EQ(mockCommandSenderDelegate.onResponseCalledTimes, 1); |
| EXPECT_EQ(mockCommandSenderDelegate.onFinalCalledTimes, 1); |
| EXPECT_EQ(mockCommandSenderDelegate.onErrorCalledTimes, 0); |
| |
| EXPECT_EQ(commandSender.GetInvokeResponseMessageCount(), 1u); |
| |
| EXPECT_EQ(GetNumActiveCommandResponderObjects(), 0u); |
| EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); |
| } |
| |
| TEST_F(TestCommandInteraction, TestCommandSenderCommandAsyncSuccessResponseFlow) |
| { |
| |
| mockCommandSenderDelegate.ResetCounter(); |
| app::CommandSender commandSender(&mockCommandSenderDelegate, &GetExchangeManager()); |
| |
| AddInvokeRequestData(&commandSender); |
| asyncCommand = true; |
| |
| EXPECT_EQ(commandSender.SendCommandRequest(GetSessionBobToAlice()), CHIP_NO_ERROR); |
| |
| DrainAndServiceIO(); |
| |
| EXPECT_EQ(mockCommandSenderDelegate.onResponseCalledTimes, 0); |
| EXPECT_EQ(mockCommandSenderDelegate.onFinalCalledTimes, 0); |
| EXPECT_EQ(mockCommandSenderDelegate.onErrorCalledTimes, 0); |
| EXPECT_EQ(GetNumActiveCommandResponderObjects(), 1u); |
| EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 2u); |
| |
| // Decrease CommandHandler refcount and send response |
| asyncCommandHandle = nullptr; |
| |
| DrainAndServiceIO(); |
| |
| EXPECT_EQ(mockCommandSenderDelegate.onResponseCalledTimes, 1); |
| EXPECT_EQ(mockCommandSenderDelegate.onFinalCalledTimes, 1); |
| EXPECT_EQ(mockCommandSenderDelegate.onErrorCalledTimes, 0); |
| |
| EXPECT_EQ(GetNumActiveCommandResponderObjects(), 0u); |
| EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); |
| } |
| |
| TEST_F(TestCommandInteraction, CommandSenderDeletedWhenResponseIsPending) |
| { |
| |
| mockCommandSenderDelegate.ResetCounter(); |
| app::CommandSender * commandSender = Platform::New<app::CommandSender>(&mockCommandSenderDelegate, &GetExchangeManager()); |
| |
| AddInvokeRequestData(commandSender); |
| asyncCommand = true; |
| |
| EXPECT_EQ(commandSender->SendCommandRequest(GetSessionBobToAlice()), CHIP_NO_ERROR); |
| |
| DrainAndServiceIO(); |
| |
| EXPECT_EQ(mockCommandSenderDelegate.onResponseCalledTimes, 0); |
| EXPECT_EQ(mockCommandSenderDelegate.onFinalCalledTimes, 0); |
| EXPECT_EQ(mockCommandSenderDelegate.onErrorCalledTimes, 0); |
| EXPECT_EQ(GetNumActiveCommandResponderObjects(), 1u); |
| EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 2u); |
| |
| // This is NOT deleting CommandSender in one of the callbacks, so we are not violating |
| // the API contract. CommandSender is deleted when no message is being processed which |
| // is a time that deleting CommandSender is considered safe. |
| Platform::Delete(commandSender); |
| |
| // Decrease CommandHandler refcount and send response |
| asyncCommandHandle = nullptr; |
| |
| DrainAndServiceIO(); |
| |
| EXPECT_EQ(mockCommandSenderDelegate.onResponseCalledTimes, 0); |
| EXPECT_EQ(mockCommandSenderDelegate.onFinalCalledTimes, 0); |
| EXPECT_EQ(mockCommandSenderDelegate.onErrorCalledTimes, 0); |
| |
| EXPECT_EQ(GetNumActiveCommandResponderObjects(), 0u); |
| EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); |
| } |
| |
| TEST_F(TestCommandInteraction, TestCommandSenderCommandSpecificResponseFlow) |
| { |
| |
| mockCommandSenderDelegate.ResetCounter(); |
| app::CommandSender commandSender(&mockCommandSenderDelegate, &GetExchangeManager()); |
| |
| AddInvokeRequestData(&commandSender, kTestCommandIdCommandSpecificResponse); |
| EXPECT_EQ(commandSender.SendCommandRequest(GetSessionBobToAlice()), CHIP_NO_ERROR); |
| |
| DrainAndServiceIO(); |
| |
| EXPECT_EQ(mockCommandSenderDelegate.onResponseCalledTimes, 1); |
| EXPECT_EQ(mockCommandSenderDelegate.onFinalCalledTimes, 1); |
| EXPECT_EQ(mockCommandSenderDelegate.onErrorCalledTimes, 0); |
| EXPECT_EQ(GetNumActiveCommandResponderObjects(), 0u); |
| EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); |
| } |
| |
| TEST_F(TestCommandInteraction, TestCommandSenderCommandFailureResponseFlow) |
| { |
| |
| mockCommandSenderDelegate.ResetCounter(); |
| app::CommandSender commandSender(&mockCommandSenderDelegate, &GetExchangeManager()); |
| |
| AddInvokeRequestData(&commandSender, kTestNonExistCommandId); |
| |
| EXPECT_EQ(commandSender.SendCommandRequest(GetSessionBobToAlice()), CHIP_NO_ERROR); |
| |
| DrainAndServiceIO(); |
| |
| EXPECT_EQ(mockCommandSenderDelegate.onResponseCalledTimes, 0); |
| EXPECT_EQ(mockCommandSenderDelegate.onFinalCalledTimes, 1); |
| EXPECT_EQ(mockCommandSenderDelegate.onErrorCalledTimes, 1); |
| EXPECT_EQ(GetNumActiveCommandResponderObjects(), 0u); |
| EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); |
| } |
| |
| TEST_F(TestCommandInteraction, TestCommandSenderAbruptDestruction) |
| { |
| |
| // |
| // Don't send back a response, just keep the CommandHandler |
| // hanging to give us enough time to do what we want with the CommandSender object. |
| // |
| sendResponse = false; |
| |
| mockCommandSenderDelegate.ResetCounter(); |
| |
| { |
| app::CommandSender commandSender(&mockCommandSenderDelegate, &GetExchangeManager()); |
| |
| AddInvokeRequestData(&commandSender, kTestCommandIdCommandSpecificResponse); |
| |
| EXPECT_EQ(commandSender.SendCommandRequest(GetSessionBobToAlice()), CHIP_NO_ERROR); |
| |
| DrainAndServiceIO(); |
| |
| // |
| // No callbacks should be invoked yet - let's validate that. |
| // |
| EXPECT_EQ(mockCommandSenderDelegate.onResponseCalledTimes, 0); |
| EXPECT_EQ(mockCommandSenderDelegate.onFinalCalledTimes, 0); |
| EXPECT_EQ(mockCommandSenderDelegate.onErrorCalledTimes, 0); |
| |
| EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 1u); |
| } |
| |
| // |
| // Upon the sender being destructed by the application, our exchange should get cleaned up too. |
| // |
| EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); |
| |
| EXPECT_EQ(GetNumActiveCommandResponderObjects(), 0u); |
| } |
| |
| TEST_F(TestCommandInteraction, TestCommandHandler_RejectMultipleIdenticalCommands) |
| { |
| |
| isCommandDispatched = false; |
| mockCommandSenderExtendedDelegate.ResetCounter(); |
| PendingResponseTrackerImpl pendingResponseTracker; |
| app::CommandSender commandSender(kCommandSenderTestOnlyMarker, &mockCommandSenderExtendedDelegate, &GetExchangeManager(), |
| &pendingResponseTracker); |
| |
| app::CommandSender::ConfigParameters configParameters; |
| configParameters.SetRemoteMaxPathsPerInvoke(2); |
| EXPECT_EQ(CHIP_NO_ERROR, commandSender.SetCommandSenderConfig(configParameters)); |
| |
| // Command ID is not important here, since the command handler should reject the commands without handling it. |
| auto commandPathParams = MakeTestCommandPath(kTestCommandIdCommandSpecificResponse); |
| |
| for (uint16_t i = 0; i < 2; i++) |
| { |
| app::CommandSender::PrepareCommandParameters prepareCommandParams; |
| prepareCommandParams.SetStartDataStruct(true); |
| prepareCommandParams.SetCommandRef(i); |
| EXPECT_EQ(CHIP_NO_ERROR, commandSender.PrepareCommand(commandPathParams, prepareCommandParams)); |
| EXPECT_EQ(CHIP_NO_ERROR, commandSender.GetCommandDataIBTLVWriter()->PutBoolean(chip::TLV::ContextTag(1), true)); |
| |
| app::CommandSender::FinishCommandParameters finishCommandParams; |
| finishCommandParams.SetEndDataStruct(true); |
| finishCommandParams.SetCommandRef(i); |
| EXPECT_EQ(CHIP_NO_ERROR, commandSender.FinishCommand(finishCommandParams)); |
| } |
| |
| EXPECT_EQ(commandSender.SendCommandRequest(GetSessionBobToAlice()), CHIP_NO_ERROR); |
| |
| DrainAndServiceIO(); |
| |
| EXPECT_EQ(mockCommandSenderExtendedDelegate.onResponseCalledTimes, 0); |
| EXPECT_EQ(mockCommandSenderExtendedDelegate.onFinalCalledTimes, 1); |
| EXPECT_EQ(mockCommandSenderExtendedDelegate.onErrorCalledTimes, 1); |
| EXPECT_FALSE(chip::isCommandDispatched); |
| |
| EXPECT_EQ(GetNumActiveCommandResponderObjects(), 0u); |
| EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); |
| } |
| |
| #if CONFIG_BUILD_FOR_HOST_UNIT_TEST |
| |
| TEST_F_FROM_FIXTURE(TestCommandInteraction, TestCommandHandler_RejectsMultipleCommandsWithIdenticalCommandRef) |
| { |
| |
| isCommandDispatched = false; |
| mockCommandSenderExtendedDelegate.ResetCounter(); |
| PendingResponseTrackerImpl pendingResponseTracker; |
| app::CommandSender commandSender(kCommandSenderTestOnlyMarker, &mockCommandSenderExtendedDelegate, &GetExchangeManager(), |
| &pendingResponseTracker); |
| |
| app::CommandSender::ConfigParameters configParameters; |
| configParameters.SetRemoteMaxPathsPerInvoke(2); |
| EXPECT_EQ(CHIP_NO_ERROR, commandSender.SetCommandSenderConfig(configParameters)); |
| |
| uint16_t numberOfCommandsToSend = 2; |
| { |
| CommandPathParams requestCommandPaths[] = { |
| MakeTestCommandPath(kTestCommandIdWithData), |
| MakeTestCommandPath(kTestCommandIdCommandSpecificResponse), |
| }; |
| |
| uint16_t hardcodedCommandRef = 0; |
| |
| for (uint16_t i = 0; i < numberOfCommandsToSend; i++) |
| { |
| app::CommandSender::PrepareCommandParameters prepareCommandParams; |
| prepareCommandParams.SetStartDataStruct(true); |
| prepareCommandParams.SetCommandRef(i); |
| EXPECT_EQ(CHIP_NO_ERROR, commandSender.PrepareCommand(requestCommandPaths[i], prepareCommandParams)); |
| EXPECT_EQ(CHIP_NO_ERROR, commandSender.GetCommandDataIBTLVWriter()->PutBoolean(chip::TLV::ContextTag(1), true)); |
| // TODO fix this comment |
| // We are taking advantage of the fact that the commandRef was set into finishCommandParams during PrepareCommand |
| // But setting it to a different value here, we are overriding what it was supposed to be. |
| app::CommandSender::FinishCommandParameters finishCommandParams; |
| finishCommandParams.SetEndDataStruct(true); |
| finishCommandParams.SetCommandRef(hardcodedCommandRef); |
| EXPECT_EQ(CHIP_NO_ERROR, commandSender.TestOnlyFinishCommand(finishCommandParams)); |
| } |
| } |
| |
| BasicCommandPathRegistry<4> basicCommandPathRegistry; |
| MockCommandResponder mockCommandResponder; |
| CommandHandlerImpl::TestOnlyOverrides testOnlyOverrides{ &basicCommandPathRegistry, &mockCommandResponder }; |
| CommandHandlerImpl commandHandler(testOnlyOverrides, &mockCommandHandlerDelegate); |
| |
| // Hackery to steal the InvokeRequest buffer from commandSender. |
| System::PacketBufferHandle commandDatabuf; |
| EXPECT_EQ(commandSender.Finalize(commandDatabuf), CHIP_NO_ERROR); |
| |
| mockCommandHandlerDelegate.ResetCounter(); |
| commandDispatchedCount = 0; |
| |
| Protocols::InteractionModel::Status status = commandHandler.ProcessInvokeRequest(std::move(commandDatabuf), false); |
| EXPECT_EQ(status, Protocols::InteractionModel::Status::InvalidAction); |
| |
| EXPECT_EQ(commandDispatchedCount, 0u); |
| } |
| |
| #endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST |
| |
| TEST_F_FROM_FIXTURE(TestCommandInteraction, TestCommandHandler_RejectMultipleCommandsWhenHandlerOnlySupportsOne) |
| { |
| |
| isCommandDispatched = false; |
| |
| mockCommandSenderExtendedDelegate.ResetCounter(); |
| PendingResponseTrackerImpl pendingResponseTracker; |
| app::CommandSender commandSender(kCommandSenderTestOnlyMarker, &mockCommandSenderExtendedDelegate, &GetExchangeManager(), |
| &pendingResponseTracker); |
| |
| app::CommandSender::ConfigParameters configParameters; |
| configParameters.SetRemoteMaxPathsPerInvoke(2); |
| EXPECT_EQ(CHIP_NO_ERROR, commandSender.SetCommandSenderConfig(configParameters)); |
| |
| uint16_t numberOfCommandsToSend = 2; |
| { |
| CommandPathParams requestCommandPaths[] = { |
| MakeTestCommandPath(kTestCommandIdWithData), |
| MakeTestCommandPath(kTestCommandIdCommandSpecificResponse), |
| }; |
| |
| for (uint16_t i = 0; i < numberOfCommandsToSend; i++) |
| { |
| app::CommandSender::PrepareCommandParameters prepareCommandParams; |
| prepareCommandParams.SetStartDataStruct(true); |
| prepareCommandParams.SetCommandRef(i); |
| EXPECT_EQ(CHIP_NO_ERROR, commandSender.PrepareCommand(requestCommandPaths[i], prepareCommandParams)); |
| EXPECT_EQ(CHIP_NO_ERROR, commandSender.GetCommandDataIBTLVWriter()->PutBoolean(chip::TLV::ContextTag(1), true)); |
| app::CommandSender::FinishCommandParameters finishCommandParams; |
| finishCommandParams.SetEndDataStruct(true); |
| finishCommandParams.SetCommandRef(i); |
| EXPECT_EQ(CHIP_NO_ERROR, commandSender.FinishCommand(finishCommandParams)); |
| } |
| } |
| |
| // Hackery to steal the InvokeRequest buffer from commandSender. |
| System::PacketBufferHandle commandDatabuf; |
| EXPECT_EQ(commandSender.Finalize(commandDatabuf), CHIP_NO_ERROR); |
| |
| mockCommandHandlerDelegate.ResetCounter(); |
| sendResponse = true; |
| commandDispatchedCount = 0; |
| |
| CommandHandlerImpl commandHandler(&mockCommandHandlerDelegate); |
| MockCommandResponder mockCommandResponder; |
| Protocols::InteractionModel::Status status = |
| commandHandler.OnInvokeCommandRequest(mockCommandResponder, std::move(commandDatabuf), false); |
| EXPECT_EQ(status, Protocols::InteractionModel::Status::InvalidAction); |
| EXPECT_TRUE(mockCommandResponder.mChunks.IsNull()); |
| |
| EXPECT_EQ(commandDispatchedCount, 0u); |
| } |
| |
| TEST_F_FROM_FIXTURE(TestCommandInteraction, TestCommandHandler_AcceptMultipleCommands) |
| { |
| |
| isCommandDispatched = false; |
| |
| mockCommandSenderExtendedDelegate.ResetCounter(); |
| PendingResponseTrackerImpl pendingResponseTracker; |
| app::CommandSender commandSender(kCommandSenderTestOnlyMarker, &mockCommandSenderExtendedDelegate, &GetExchangeManager(), |
| &pendingResponseTracker); |
| |
| app::CommandSender::ConfigParameters configParameters; |
| configParameters.SetRemoteMaxPathsPerInvoke(2); |
| EXPECT_EQ(CHIP_NO_ERROR, commandSender.SetCommandSenderConfig(configParameters)); |
| |
| uint16_t numberOfCommandsToSend = 2; |
| { |
| CommandPathParams requestCommandPaths[] = { |
| MakeTestCommandPath(kTestCommandIdWithData), |
| MakeTestCommandPath(kTestCommandIdCommandSpecificResponse), |
| }; |
| |
| for (uint16_t i = 0; i < numberOfCommandsToSend; i++) |
| { |
| app::CommandSender::PrepareCommandParameters prepareCommandParams; |
| prepareCommandParams.SetStartDataStruct(true); |
| prepareCommandParams.SetCommandRef(i); |
| EXPECT_EQ(CHIP_NO_ERROR, commandSender.PrepareCommand(requestCommandPaths[i], prepareCommandParams)); |
| EXPECT_EQ(CHIP_NO_ERROR, commandSender.GetCommandDataIBTLVWriter()->PutBoolean(chip::TLV::ContextTag(1), true)); |
| app::CommandSender::FinishCommandParameters finishCommandParams; |
| finishCommandParams.SetEndDataStruct(true); |
| finishCommandParams.SetCommandRef(i); |
| EXPECT_EQ(CHIP_NO_ERROR, commandSender.FinishCommand(finishCommandParams)); |
| } |
| commandSender.MoveToState(app::CommandSender::State::AddedCommand); |
| } |
| |
| BasicCommandPathRegistry<4> basicCommandPathRegistry; |
| MockCommandResponder mockCommandResponder; |
| CommandHandlerImpl::TestOnlyOverrides testOnlyOverrides{ &basicCommandPathRegistry, &mockCommandResponder }; |
| CommandHandlerImpl commandHandler(testOnlyOverrides, &mockCommandHandlerDelegate); |
| |
| // Hackery to steal the InvokeRequest buffer from commandSender. |
| System::PacketBufferHandle commandDatabuf; |
| EXPECT_EQ(commandSender.Finalize(commandDatabuf), CHIP_NO_ERROR); |
| |
| sendResponse = true; |
| mockCommandHandlerDelegate.ResetCounter(); |
| commandDispatchedCount = 0; |
| |
| Protocols::InteractionModel::Status status = commandHandler.ProcessInvokeRequest(std::move(commandDatabuf), false); |
| EXPECT_EQ(status, Protocols::InteractionModel::Status::Success); |
| |
| EXPECT_EQ(commandDispatchedCount, 2u); |
| } |
| |
| TEST_F_FROM_FIXTURE(TestCommandInteraction, TestCommandHandler_FillUpInvokeResponseMessageWhereSecondResponseIsStatusResponse) |
| { |
| BasicCommandPathRegistry<4> basicCommandPathRegistry; |
| MockCommandResponder mockCommandResponder; |
| CommandHandlerImpl::TestOnlyOverrides testOnlyOverrides{ &basicCommandPathRegistry, &mockCommandResponder }; |
| CommandHandlerImpl commandHandler(testOnlyOverrides, &mockCommandHandlerDelegate); |
| |
| commandHandler.mReserveSpaceForMoreChunkMessages = true; |
| ConcreteCommandPath requestCommandPath1 = { kTestEndpointId, kTestClusterId, kTestCommandIdFillResponseMessage }; |
| ConcreteCommandPath requestCommandPath2 = { kTestEndpointId, kTestClusterId, kTestCommandIdCommandSpecificResponse }; |
| |
| EXPECT_EQ(basicCommandPathRegistry.Add(requestCommandPath1, std::make_optional<uint16_t>(static_cast<uint16_t>(1))), |
| CHIP_NO_ERROR); |
| EXPECT_EQ(basicCommandPathRegistry.Add(requestCommandPath2, std::make_optional<uint16_t>(static_cast<uint16_t>(2))), |
| CHIP_NO_ERROR); |
| |
| uint32_t sizeToLeave = 0; |
| FillCurrentInvokeResponseBuffer(&commandHandler, requestCommandPath1, sizeToLeave); |
| uint32_t remainingSize = commandHandler.mInvokeResponseBuilder.GetWriter()->GetRemainingFreeLength(); |
| EXPECT_EQ(remainingSize, sizeToLeave); |
| |
| AddInvokeResponseData(&commandHandler, /* aNeedStatusCode = */ true, kTestCommandIdCommandSpecificResponse, |
| kTestCommandIdCommandSpecificResponse); |
| |
| remainingSize = commandHandler.mInvokeResponseBuilder.GetWriter()->GetRemainingFreeLength(); |
| EXPECT_GT(remainingSize, sizeToLeave); |
| } |
| TEST_F_FROM_FIXTURE(TestCommandInteraction, |
| TestCommandHandler_FillUpInvokeResponseMessageWhereSecondResponseIsDataResponsePrimative) |
| { |
| BasicCommandPathRegistry<4> basicCommandPathRegistry; |
| MockCommandResponder mockCommandResponder; |
| CommandHandlerImpl::TestOnlyOverrides testOnlyOverrides{ &basicCommandPathRegistry, &mockCommandResponder }; |
| CommandHandlerImpl commandHandler(testOnlyOverrides, &mockCommandHandlerDelegate); |
| |
| commandHandler.mReserveSpaceForMoreChunkMessages = true; |
| ConcreteCommandPath requestCommandPath1 = { kTestEndpointId, kTestClusterId, kTestCommandIdFillResponseMessage }; |
| ConcreteCommandPath requestCommandPath2 = { kTestEndpointId, kTestClusterId, kTestCommandIdCommandSpecificResponse }; |
| |
| EXPECT_EQ(basicCommandPathRegistry.Add(requestCommandPath1, std::make_optional<uint16_t>(static_cast<uint16_t>(1))), |
| CHIP_NO_ERROR); |
| EXPECT_EQ(basicCommandPathRegistry.Add(requestCommandPath2, std::make_optional<uint16_t>(static_cast<uint16_t>(2))), |
| CHIP_NO_ERROR); |
| |
| uint32_t sizeToLeave = 0; |
| FillCurrentInvokeResponseBuffer(&commandHandler, requestCommandPath1, sizeToLeave); |
| uint32_t remainingSize = commandHandler.mInvokeResponseBuilder.GetWriter()->GetRemainingFreeLength(); |
| EXPECT_EQ(remainingSize, sizeToLeave); |
| |
| AddInvokeResponseData(&commandHandler, /* aNeedStatusCode = */ false, kTestCommandIdCommandSpecificResponse, |
| kTestCommandIdCommandSpecificResponse); |
| |
| remainingSize = commandHandler.mInvokeResponseBuilder.GetWriter()->GetRemainingFreeLength(); |
| EXPECT_GT(remainingSize, sizeToLeave); |
| } |
| |
| TEST_F_FROM_FIXTURE(TestCommandInteraction, TestCommandHandler_FillUpInvokeResponseMessageWhereSecondResponseIsDataResponse) |
| { |
| BasicCommandPathRegistry<4> basicCommandPathRegistry; |
| MockCommandResponder mockCommandResponder; |
| CommandHandlerImpl::TestOnlyOverrides testOnlyOverrides{ &basicCommandPathRegistry, &mockCommandResponder }; |
| CommandHandlerImpl commandHandler(testOnlyOverrides, &mockCommandHandlerDelegate); |
| commandHandler.mReserveSpaceForMoreChunkMessages = true; |
| ConcreteCommandPath requestCommandPath1 = { kTestEndpointId, kTestClusterId, kTestCommandIdFillResponseMessage }; |
| ConcreteCommandPath requestCommandPath2 = { kTestEndpointId, kTestClusterId, kTestCommandIdCommandSpecificResponse }; |
| |
| EXPECT_EQ(basicCommandPathRegistry.Add(requestCommandPath1, std::make_optional<uint16_t>(static_cast<uint16_t>(1))), |
| CHIP_NO_ERROR); |
| EXPECT_EQ(basicCommandPathRegistry.Add(requestCommandPath2, std::make_optional<uint16_t>(static_cast<uint16_t>(2))), |
| CHIP_NO_ERROR); |
| |
| uint32_t sizeToLeave = 0; |
| FillCurrentInvokeResponseBuffer(&commandHandler, requestCommandPath1, sizeToLeave); |
| uint32_t remainingSize = commandHandler.mInvokeResponseBuilder.GetWriter()->GetRemainingFreeLength(); |
| EXPECT_EQ(remainingSize, sizeToLeave); |
| |
| uint32_t sizeToFill = 50; |
| ForcedSizeBuffer responseData(sizeToFill); |
| EXPECT_EQ(commandHandler.AddResponseData(requestCommandPath2, responseData.GetCommandId(), responseData), CHIP_NO_ERROR); |
| |
| remainingSize = commandHandler.mInvokeResponseBuilder.GetWriter()->GetRemainingFreeLength(); |
| EXPECT_GT(remainingSize, sizeToLeave); |
| } |
| |
| #if CONFIG_BUILD_FOR_HOST_UNIT_TEST |
| // |
| // This test needs a special unit-test only API being exposed in ExchangeContext to be able to correctly simulate |
| // the release of a session on the exchange. |
| // |
| TEST_F_FROM_FIXTURE(TestCommandInteraction, TestCommandHandler_ReleaseWithExchangeClosed) |
| { |
| |
| app::CommandSender commandSender(&mockCommandSenderDelegate, &GetExchangeManager()); |
| |
| AddInvokeRequestData(&commandSender); |
| asyncCommandHandle = nullptr; |
| asyncCommand = true; |
| |
| EXPECT_EQ(commandSender.SendCommandRequest(GetSessionBobToAlice()), CHIP_NO_ERROR); |
| |
| DrainAndServiceIO(); |
| |
| // Verify that async command handle has been allocated |
| ASSERT_NE(asyncCommandHandle.Get(), nullptr); |
| |
| // Mimic closure of the exchange that would happen on a session release and verify that releasing the handle there-after |
| // is handled gracefully. |
| asyncCommandHandle.Get()->GetExchangeContext()->GetSessionHolder().Release(); |
| asyncCommandHandle.Get()->GetExchangeContext()->OnSessionReleased(); |
| asyncCommandHandle = nullptr; |
| } |
| #endif |
| |
| } // namespace app |
| } // namespace chip |