diff --git a/docs/ERROR_CODES.md b/docs/ERROR_CODES.md index 787ea58659c460..f6ba2767ca40a1 100644 --- a/docs/ERROR_CODES.md +++ b/docs/ERROR_CODES.md @@ -126,6 +126,7 @@ This file was **AUTOMATICALLY** generated by | 182 | 0xB6 | `CHIP_ERROR_IM_MALFORMED_EVENT_PATH_IB` | | 185 | 0xB9 | `CHIP_ERROR_IM_MALFORMED_COMMAND_DATA_IB` | | 186 | 0xBA | `CHIP_ERROR_IM_MALFORMED_EVENT_DATA_IB` | +| 187 | 0xBB | `CHIP_ERROR_MAXIMUM_PATHS_PER_INVOKE_EXCEEDED` | | 188 | 0xBC | `CHIP_ERROR_PEER_NODE_NOT_FOUND` | | 189 | 0xBD | `CHIP_ERROR_HSM` | | 191 | 0xBF | `CHIP_ERROR_REAL_TIME_NOT_SYNCED` | diff --git a/src/app/CommandSender.cpp b/src/app/CommandSender.cpp index d579845d2e5d5a..a17022d80a1f77 100644 --- a/src/app/CommandSender.cpp +++ b/src/app/CommandSender.cpp @@ -32,6 +32,33 @@ namespace chip { namespace app { +namespace { + +// Gets the CommandRef if available. Error returned if we expected CommandRef and it wasn't +// provided in the response. +template +CHIP_ERROR GetRef(ParserT aParser, Optional & aRef, bool commandRefExpected) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + uint16_t ref; + err = aParser.GetRef(&ref); + + VerifyOrReturnError(err == CHIP_NO_ERROR || err == CHIP_END_OF_TLV, err); + if (err == CHIP_END_OF_TLV) + { + if (commandRefExpected) + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + aRef = NullOptional; + return CHIP_NO_ERROR; + } + + aRef = MakeOptional(ref); + return CHIP_NO_ERROR; +} + +} // namespace CommandSender::CommandSender(Callback * apCallback, Messaging::ExchangeManager * apExchangeMgr, bool aIsTimedRequest, bool aSuppressResponse) : @@ -296,8 +323,10 @@ CHIP_ERROR CommandSender::ProcessInvokeResponseIB(InvokeResponseIB::Parser & aIn StatusIB statusIB; { - bool hasDataResponse = false; + bool commandRefExpected = (mFinishedCommandCount > 1); + bool hasDataResponse = false; TLV::TLVReader commandDataReader; + AdditionalResponseData additionalResponseData; CommandStatusIB::Parser commandStatus; err = aInvokeResponse.GetStatus(&commandStatus); @@ -312,6 +341,7 @@ CHIP_ERROR CommandSender::ProcessInvokeResponseIB(InvokeResponseIB::Parser & aIn StatusIB::Parser status; commandStatus.GetErrorStatus(&status); ReturnErrorOnFailure(status.DecodeStatusIB(statusIB)); + ReturnErrorOnFailure(GetRef(commandStatus, additionalResponseData.mCommandRef, commandRefExpected)); } else if (CHIP_END_OF_TLV == err) { @@ -323,6 +353,7 @@ CHIP_ERROR CommandSender::ProcessInvokeResponseIB(InvokeResponseIB::Parser & aIn ReturnErrorOnFailure(commandPath.GetClusterId(&clusterId)); ReturnErrorOnFailure(commandPath.GetCommandId(&commandId)); commandData.GetFields(&commandDataReader); + ReturnErrorOnFailure(GetRef(commandData, additionalResponseData.mCommandRef, commandRefExpected)); err = CHIP_NO_ERROR; hasDataResponse = true; } @@ -355,8 +386,8 @@ CHIP_ERROR CommandSender::ProcessInvokeResponseIB(InvokeResponseIB::Parser & aIn { if (statusIB.IsSuccess()) { - mpCallback->OnResponse(this, ConcreteCommandPath(endpointId, clusterId, commandId), statusIB, - hasDataResponse ? &commandDataReader : nullptr); + mpCallback->OnResponseWithAdditionalData(this, ConcreteCommandPath(endpointId, clusterId, commandId), statusIB, + hasDataResponse ? &commandDataReader : nullptr, additionalResponseData); } else { @@ -367,14 +398,38 @@ CHIP_ERROR CommandSender::ProcessInvokeResponseIB(InvokeResponseIB::Parser & aIn return CHIP_NO_ERROR; } -CHIP_ERROR CommandSender::PrepareCommand(const CommandPathParams & aCommandPathParams, bool aStartDataStruct) +CHIP_ERROR CommandSender::SetCommandSenderConfig(CommandSender::ConfigParameters & aConfigParams) +{ +#if CHIP_CONFIG_SENDING_BATCH_COMMANDS_ENABLED + VerifyOrReturnError(mState == State::Idle, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(aConfigParams.mRemoteMaxPathsPerInvoke > 0, CHIP_ERROR_INVALID_ARGUMENT); + + mRemoteMaxPathsPerInvoke = aConfigParams.mRemoteMaxPathsPerInvoke; + mBatchCommandsEnabled = (aConfigParams.mRemoteMaxPathsPerInvoke > 1); + return CHIP_NO_ERROR; +#else + VerifyOrReturnError(aConfigParams.mRemoteMaxPathsPerInvoke == 1, CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); + return CHIP_NO_ERROR; +#endif +} + +CHIP_ERROR CommandSender::PrepareCommand(const CommandPathParams & aCommandPathParams, AdditionalCommandParameters & aOptionalArgs) { ReturnErrorOnFailure(AllocateBuffer()); // - // We must not be in the middle of preparing a command, or having prepared or sent one. + // We must not be in the middle of preparing a command, and must not have already sent InvokeRequestMessage. // - VerifyOrReturnError(mState == State::Idle, CHIP_ERROR_INCORRECT_STATE); + bool canAddAnotherCommand = (mState == State::AddedCommand && mBatchCommandsEnabled); + VerifyOrReturnError(mState == State::Idle || canAddAnotherCommand, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mFinishedCommandCount < mRemoteMaxPathsPerInvoke, CHIP_ERROR_MAXIMUM_PATHS_PER_INVOKE_EXCEEDED); + + VerifyOrReturnError(!aOptionalArgs.mCommandRef.HasValue(), CHIP_ERROR_INVALID_ARGUMENT); + if (mBatchCommandsEnabled) + { + aOptionalArgs.mCommandRef.SetValue(mFinishedCommandCount); + } + InvokeRequests::Builder & invokeRequests = mInvokeRequestBuilder.GetInvokeRequests(); CommandDataIB::Builder & invokeRequest = invokeRequests.CreateCommandData(); ReturnErrorOnFailure(invokeRequests.GetError()); @@ -382,7 +437,7 @@ CHIP_ERROR CommandSender::PrepareCommand(const CommandPathParams & aCommandPathP ReturnErrorOnFailure(invokeRequest.GetError()); ReturnErrorOnFailure(path.Encode(aCommandPathParams)); - if (aStartDataStruct) + if (aOptionalArgs.mStartOrEndDataStruct) { ReturnErrorOnFailure(invokeRequest.GetWriter()->StartContainer(TLV::ContextTag(CommandDataIB::Tag::kFields), TLV::kTLVType_Structure, mDataElementContainerType)); @@ -392,7 +447,7 @@ CHIP_ERROR CommandSender::PrepareCommand(const CommandPathParams & aCommandPathP return CHIP_NO_ERROR; } -CHIP_ERROR CommandSender::FinishCommand(bool aEndDataStruct) +CHIP_ERROR CommandSender::FinishCommand(const AdditionalCommandParameters & aOptionalArgs) { CHIP_ERROR err = CHIP_NO_ERROR; @@ -400,17 +455,27 @@ CHIP_ERROR CommandSender::FinishCommand(bool aEndDataStruct) CommandDataIB::Builder & commandData = mInvokeRequestBuilder.GetInvokeRequests().GetCommandData(); - if (aEndDataStruct) + if (aOptionalArgs.mStartOrEndDataStruct) { ReturnErrorOnFailure(commandData.GetWriter()->EndContainer(mDataElementContainerType)); } + if (mBatchCommandsEnabled) + { + // If error below occurs, aOptionalArgs.mCommandRef was modified since PerpareCommand was called. + VerifyOrReturnError(aOptionalArgs.mCommandRef.HasValue(), CHIP_ERROR_INVALID_ARGUMENT); + } + + if (aOptionalArgs.mCommandRef.HasValue()) + { + ReturnErrorOnFailure(commandData.Ref(aOptionalArgs.mCommandRef.Value())); + } + ReturnErrorOnFailure(commandData.EndOfCommandDataIB()); - ReturnErrorOnFailure(mInvokeRequestBuilder.GetInvokeRequests().EndOfInvokeRequests()); - ReturnErrorOnFailure(mInvokeRequestBuilder.EndOfInvokeRequestMessage()); MoveToState(State::AddedCommand); + mFinishedCommandCount++; return CHIP_NO_ERROR; } @@ -424,9 +489,10 @@ TLV::TLVWriter * CommandSender::GetCommandDataIBTLVWriter() return mInvokeRequestBuilder.GetInvokeRequests().GetCommandData().GetWriter(); } -CHIP_ERROR CommandSender::FinishCommand(const Optional & aTimedInvokeTimeoutMs) +CHIP_ERROR CommandSender::FinishCommand(const Optional & aTimedInvokeTimeoutMs, + const AdditionalCommandParameters & aOptionalArgs) { - ReturnErrorOnFailure(FinishCommand(/* aEndDataStruct = */ false)); + ReturnErrorOnFailure(FinishCommand(aOptionalArgs)); if (!mTimedInvokeTimeoutMs.HasValue()) { mTimedInvokeTimeoutMs = aTimedInvokeTimeoutMs; @@ -442,6 +508,8 @@ CHIP_ERROR CommandSender::FinishCommand(const Optional & aTimedInvokeT CHIP_ERROR CommandSender::Finalize(System::PacketBufferHandle & commandPacket) { VerifyOrReturnError(mState == State::AddedCommand, CHIP_ERROR_INCORRECT_STATE); + ReturnErrorOnFailure(mInvokeRequestBuilder.GetInvokeRequests().EndOfInvokeRequests()); + ReturnErrorOnFailure(mInvokeRequestBuilder.EndOfInvokeRequestMessage()); return mCommandMessageWriter.Finalize(&commandPacket); } diff --git a/src/app/CommandSender.h b/src/app/CommandSender.h index 194c4535457562..4a1744e8113b7e 100644 --- a/src/app/CommandSender.h +++ b/src/app/CommandSender.h @@ -53,11 +53,55 @@ namespace app { class CommandSender final : public Messaging::ExchangeDelegate { public: + // CommandSender::Callback::OnResponse is public SDK API, so we cannot break source + // compatibility for it. To allow for additional values to be added at a future time + // without constantly changing the function's declaration parameter list, we are + // defining the struct AdditionalResponseData and adding that to the parameter list + // to allow for future extendability. + struct AdditionalResponseData + { + Optional mCommandRef; + }; + class Callback { public: virtual ~Callback() = default; + /** + * OnResponseWithAdditionalData will be called when a successful response from the server has been received and processed. + * Specifically: + * - When a status code is received and it is IM::Success, aData will be nullptr. + * - When a data response is received, aData will point to a valid TLVReader initialized to point at the struct container + * that contains the data payload (callee will still need to open and process the container). + * + * This OnResponseWithAdditionalData is similar to OnResponse mentioned below, except it contains an additional parameter + * `AdditionalResponseData`. This was added in Matter 1.3 to not break backward compatibility, but is extendable in the + * future to provide additional response data by only making changes to `AdditionalResponseData`, and not all the potential + * callees. + * + * The CommandSender object MUST continue to exist after this call is completed. The application shall wait until it + * receives an OnDone call to destroy the object. + * + * It is advised that subclass should only override this or `OnResponse`. But, it shouldn't actually matter if both are + * overridden, just that `OnResponse` will never be called by CommandSender directly. + * + * @param[in] apCommandSender The command sender object that initiated the command transaction. + * @param[in] aPath The command path field in invoke command response. + * @param[in] aStatusIB It will always have a success status. If apData is null, it can be any success status, + * including possibly a cluster-specific one. If apData is not null it aStatusIB will always + * be a generic SUCCESS status with no-cluster specific information. + * @param[in] apData The command data, will be nullptr if the server returns a StatusIB. + * @param[in] aAdditionalResponseData + * Additional response data that comes within the InvokeResponseMessage. + */ + virtual void OnResponseWithAdditionalData(CommandSender * apCommandSender, const ConcreteCommandPath & aPath, + const StatusIB & aStatusIB, TLV::TLVReader * apData, + const AdditionalResponseData & aAdditionalResponseData) + { + OnResponse(apCommandSender, aPath, aStatusIB, apData); + } + /** * OnResponse will be called when a successful response from server has been received and processed. Specifically: * - When a status code is received and it is IM::Success, aData will be nullptr. @@ -67,6 +111,9 @@ class CommandSender final : public Messaging::ExchangeDelegate * The CommandSender object MUST continue to exist after this call is completed. The application shall wait until it * receives an OnDone call to destroy the object. * + * It is advised that subclass should only override this or `OnResponseWithAdditionalData`. But, it shouldn't actually + * matter if both are overridden, just that `OnResponse` will never be called by CommandSender directly. + * * @param[in] apCommandSender The command sender object that initiated the command transaction. * @param[in] aPath The command path field in invoke command response. * @param[in] aStatusIB It will always have a success status. If apData is null, it can be any success status, @@ -114,6 +161,48 @@ class CommandSender final : public Messaging::ExchangeDelegate virtual void OnDone(CommandSender * apCommandSender) = 0; }; + // SetCommandSenderConfig is a public SDK API, so we cannot break source compatibility + // for it. By having parameters to that API use this struct instead of individual + // function arguments, we centralize required changes to one file when adding new + // funtionality. + struct ConfigParameters + { + ConfigParameters & SetRemoteMaxPathsPerInvoke(uint16_t aRemoteMaxPathsPerInvoke) + { + mRemoteMaxPathsPerInvoke = aRemoteMaxPathsPerInvoke; + return *this; + } + + // If mRemoteMaxPathsPerInvoke is 1, this will allow the CommandSender client to contain only one command and + // doesn't enforce other batch commands requirements. + uint16_t mRemoteMaxPathsPerInvoke = 1; + }; + + // PrepareCommand and FinishCommand are public SDK APIs, so we cannot break source + // compatibility for them. By having parameters to those APIs use this struct instead + // of individual function arguments, we centralize required changes to one file + // when adding new functionality. + struct AdditionalCommandParameters + { + // gcc bug requires us to have the constructor below + // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96645 + AdditionalCommandParameters() {} + + AdditionalCommandParameters & SetStartOrEndDataStruct(bool aStartOrEndDataStruct) + { + mStartOrEndDataStruct = aStartOrEndDataStruct; + return *this; + } + + // From the perspective of `PrepareCommand`, this is an out parameter. This value will be + // set by `PrepareCommand` and is expected to be unchanged by caller until it is provided + // to `FinishCommand`. + Optional mCommandRef; + // For both `PrepareCommand` and `FinishCommand` this is an in parameter. It must have + // the same value when calling `PrepareCommand` and `FinishCommand` for a given command. + bool mStartOrEndDataStruct = false; + }; + /* * Constructor. * @@ -124,9 +213,49 @@ class CommandSender final : public Messaging::ExchangeDelegate CommandSender(Callback * apCallback, Messaging::ExchangeManager * apExchangeMgr, bool aIsTimedRequest = false, bool aSuppressResponse = false); ~CommandSender(); - CHIP_ERROR PrepareCommand(const CommandPathParams & aCommandPathParams, bool aStartDataStruct = true); - CHIP_ERROR FinishCommand(bool aEndDataStruct = true); + + /** + * Enables additional features of CommandSender, for example sending batch commands. + * + * In the case of enabling batch commands, once set it ensures that commands contain all + * required data elements while building the InvokeRequestMessage. This must be called + * before PrepareCommand. + * + * @param [in] aConfigParams contains information to configure CommandSender behavior, + * such as such as allowing a max number of paths per invoke greater than one, + * based on how many paths the remote peer claims to support. + * + * @return CHIP_ERROR_INCORRECT_STATE + * If device has previously called `PrepareCommand`. + * @return CHIP_ERROR_INVALID_ARGUMENT + * Invalid argument value. + * @return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE + * Device has not enabled CHIP_CONFIG_SENDING_BATCH_COMMANDS_ENABLED. + */ + CHIP_ERROR SetCommandSenderConfig(ConfigParameters & aConfigParams); + + CHIP_ERROR PrepareCommand(const CommandPathParams & aCommandPathParams, AdditionalCommandParameters & aOptionalArgs); + + [[deprecated("PrepareCommand should migrate to calling PrepareCommand with AdditionalCommandParameters")]] CHIP_ERROR + PrepareCommand(const CommandPathParams & aCommandPathParams, bool aStartDataStruct = true) + { + AdditionalCommandParameters optionalArgs; + optionalArgs.SetStartOrEndDataStruct(aStartDataStruct); + return PrepareCommand(aCommandPathParams, optionalArgs); + } + + CHIP_ERROR FinishCommand(const AdditionalCommandParameters & aOptionalArgs); + + [[deprecated("FinishCommand should migrate to calling FinishCommand with AdditionalCommandParameters")]] CHIP_ERROR + FinishCommand(bool aEndDataStruct = true) + { + AdditionalCommandParameters optionalArgs; + optionalArgs.SetStartOrEndDataStruct(aEndDataStruct); + return FinishCommand(optionalArgs); + } + TLV::TLVWriter * GetCommandDataIBTLVWriter(); + /** * API for adding a data request. The template parameter T is generally * expected to be a ClusterName::Commands::CommandName::Type struct, but any @@ -137,9 +266,16 @@ class CommandSender final : public Messaging::ExchangeDelegate * @param [in] aData The data for the request. */ template = 0> + CHIP_ERROR AddRequestData(const CommandPathParams & aCommandPath, const CommandDataT & aData, + AdditionalCommandParameters & aOptionalArgs) + { + return AddRequestData(aCommandPath, aData, NullOptional, aOptionalArgs); + } + template = 0> CHIP_ERROR AddRequestData(const CommandPathParams & aCommandPath, const CommandDataT & aData) { - return AddRequestData(aCommandPath, aData, NullOptional); + AdditionalCommandParameters optionalArgs; + return AddRequestData(aCommandPath, aData, optionalArgs); } /** @@ -149,14 +285,29 @@ class CommandSender final : public Messaging::ExchangeDelegate */ template CHIP_ERROR AddRequestData(const CommandPathParams & aCommandPath, const CommandDataT & aData, - const Optional & aTimedInvokeTimeoutMs) + const Optional & aTimedInvokeTimeoutMs, AdditionalCommandParameters & aOptionalArgs) { VerifyOrReturnError(!CommandDataT::MustUseTimedInvoke() || aTimedInvokeTimeoutMs.HasValue(), CHIP_ERROR_INVALID_ARGUMENT); + // AddRequestDataInternal encodes the data and requires mStartOrEndDataStruct to be false. + VerifyOrReturnError(aOptionalArgs.mStartOrEndDataStruct == false, CHIP_ERROR_INVALID_ARGUMENT); - return AddRequestDataInternal(aCommandPath, aData, aTimedInvokeTimeoutMs); + return AddRequestDataInternal(aCommandPath, aData, aTimedInvokeTimeoutMs, aOptionalArgs); + } + template + CHIP_ERROR AddRequestData(const CommandPathParams & aCommandPath, const CommandDataT & aData, + const Optional & aTimedInvokeTimeoutMs) + { + AdditionalCommandParameters optionalArgs; + return AddRequestData(aCommandPath, aData, aTimedInvokeTimeoutMs, optionalArgs); } - CHIP_ERROR FinishCommand(const Optional & aTimedInvokeTimeoutMs); + CHIP_ERROR FinishCommand(const Optional & aTimedInvokeTimeoutMs, const AdditionalCommandParameters & aOptionalArgs); + + CHIP_ERROR FinishCommand(const Optional & aTimedInvokeTimeoutMs) + { + AdditionalCommandParameters optionalArgs; + return FinishCommand(aTimedInvokeTimeoutMs, optionalArgs); + } #if CONFIG_BUILD_FOR_HOST_UNIT_TEST /** @@ -164,11 +315,20 @@ class CommandSender final : public Messaging::ExchangeDelegate * guaranteed to fail due to requiring a timed invoke but not providing a * timeout parameter. For use in tests only. */ + template + CHIP_ERROR AddRequestDataNoTimedCheck(const CommandPathParams & aCommandPath, const CommandDataT & aData, + const Optional & aTimedInvokeTimeoutMs, + AdditionalCommandParameters & aOptionalArgs) + { + return AddRequestDataInternal(aCommandPath, aData, aTimedInvokeTimeoutMs, aOptionalArgs); + } + template CHIP_ERROR AddRequestDataNoTimedCheck(const CommandPathParams & aCommandPath, const CommandDataT & aData, const Optional & aTimedInvokeTimeoutMs) { - return AddRequestDataInternal(aCommandPath, aData, aTimedInvokeTimeoutMs); + AdditionalCommandParameters optionalArgs; + return AddRequestDataNoTimedCheck(aCommandPath, aData, aTimedInvokeTimeoutMs, optionalArgs); } /** @@ -183,13 +343,13 @@ class CommandSender final : public Messaging::ExchangeDelegate private: template CHIP_ERROR AddRequestDataInternal(const CommandPathParams & aCommandPath, const CommandDataT & aData, - const Optional & aTimedInvokeTimeoutMs) + const Optional & aTimedInvokeTimeoutMs, AdditionalCommandParameters & aOptionalArgs) { - ReturnErrorOnFailure(PrepareCommand(aCommandPath, /* aStartDataStruct = */ false)); + ReturnErrorOnFailure(PrepareCommand(aCommandPath, aOptionalArgs)); TLV::TLVWriter * writer = GetCommandDataIBTLVWriter(); VerifyOrReturnError(writer != nullptr, CHIP_ERROR_INCORRECT_STATE); ReturnErrorOnFailure(DataModel::Encode(*writer, TLV::ContextTag(CommandDataIB::Tag::kFields), aData)); - return FinishCommand(aTimedInvokeTimeoutMs); + return FinishCommand(aTimedInvokeTimeoutMs, aOptionalArgs); } public: @@ -288,12 +448,16 @@ class CommandSender final : public Messaging::ExchangeDelegate // invoke. Optional mTimedInvokeTimeoutMs; TLV::TLVType mDataElementContainerType = TLV::kTLVType_NotSpecified; - bool mSuppressResponse = false; - bool mTimedRequest = false; State mState = State::Idle; chip::System::PacketBufferTLVWriter mCommandMessageWriter; - bool mBufferAllocated = false; + uint16_t mFinishedCommandCount = 0; + uint16_t mRemoteMaxPathsPerInvoke = 1; + + bool mSuppressResponse = false; + bool mTimedRequest = false; + bool mBufferAllocated = false; + bool mBatchCommandsEnabled = false; }; } // namespace app diff --git a/src/app/tests/TestCommandInteraction.cpp b/src/app/tests/TestCommandInteraction.cpp index 0ebe88e3515def..5ac6e53c9ae8b7 100644 --- a/src/app/tests/TestCommandInteraction.cpp +++ b/src/app/tests/TestCommandInteraction.cpp @@ -1107,15 +1107,18 @@ void TestCommandInteraction::TestCommandHandlerInvalidMessageAsync(nlTestSuite * err = commandSender.SendCommandRequest(ctx.GetSessionBobToAlice()); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); - // Warning! This test is not testing what the testname is suggesting. Because the invoke request is malformed, - // DispatchSingleClusterCommand is never called and asyncCommandHandle is never set. Possible fix to this test - // might be incoming when CommandSender is capable of sending multiple invoke command requests. + ctx.DrainAndServiceIO(); - // Decrease CommandHandler refcount and send response + // Error status response has been sent already; it's not waiting for the handle. + NL_TEST_ASSERT(apSuite, + mockCommandSenderDelegate.onResponseCalledTimes == 0 && mockCommandSenderDelegate.onFinalCalledTimes == 1 && + mockCommandSenderDelegate.onErrorCalledTimes == 1); + NL_TEST_ASSERT(apSuite, GetNumActiveHandlerObjects() == 1); + NL_TEST_ASSERT(apSuite, asyncCommand == false); + + // Decrease CommandHandler refcount. No additional message should be sent since error response already sent. asyncCommandHandle = nullptr; - // Prevent breaking other tests. - asyncCommand = false; ctx.DrainAndServiceIO(); NL_TEST_ASSERT(apSuite, @@ -1391,7 +1394,7 @@ void TestCommandInteraction::TestCommandHandlerRejectMultipleIdenticalCommands(n commandSender.AllocateBuffer(); - // CommandSender does not support sending multiple commands with public API, so we craft a message manually. + // TODO(#30453): CommandSender does support sending multiple commands, update this test to use that. for (int i = 0; i < 2; i++) { InvokeRequests::Builder & invokeRequests = commandSender.mInvokeRequestBuilder.GetInvokeRequests(); @@ -1411,9 +1414,6 @@ void TestCommandInteraction::TestCommandHandlerRejectMultipleIdenticalCommands(n NL_TEST_ASSERT(apSuite, CHIP_NO_ERROR == invokeRequest.EndOfCommandDataIB()); } - NL_TEST_ASSERT(apSuite, CHIP_NO_ERROR == commandSender.mInvokeRequestBuilder.GetInvokeRequests().EndOfInvokeRequests()); - NL_TEST_ASSERT(apSuite, CHIP_NO_ERROR == commandSender.mInvokeRequestBuilder.EndOfInvokeRequestMessage()); - commandSender.MoveToState(app::CommandSender::State::AddedCommand); } @@ -1453,7 +1453,7 @@ void TestCommandInteraction::TestCommandHandlerRejectsMultipleCommandsWithIdenti commandSender.AllocateBuffer(); - // CommandSender does not support sending multiple commands with public API, so we craft a message manaully. + // TODO(#30453): CommandSender does support sending multiple commands, update this test to use that. for (size_t i = 0; i < numberOfCommandsToSend; i++) { InvokeRequests::Builder & invokeRequests = commandSender.mInvokeRequestBuilder.GetInvokeRequests(); @@ -1474,9 +1474,6 @@ void TestCommandInteraction::TestCommandHandlerRejectsMultipleCommandsWithIdenti NL_TEST_ASSERT(apSuite, CHIP_NO_ERROR == invokeRequest.EndOfCommandDataIB()); } - NL_TEST_ASSERT(apSuite, CHIP_NO_ERROR == commandSender.mInvokeRequestBuilder.GetInvokeRequests().EndOfInvokeRequests()); - NL_TEST_ASSERT(apSuite, CHIP_NO_ERROR == commandSender.mInvokeRequestBuilder.EndOfInvokeRequestMessage()); - commandSender.MoveToState(app::CommandSender::State::AddedCommand); } @@ -1529,7 +1526,7 @@ void TestCommandInteraction::TestCommandHandlerRejectMultipleCommandsWhenHandler commandSender.AllocateBuffer(); - // CommandSender does not support sending multiple commands with public API, so we craft a message manaully. + // TODO(#30453): CommandSender does support sending multiple commands, update this test to use that. for (size_t i = 0; i < numberOfCommandsToSend; i++) { InvokeRequests::Builder & invokeRequests = commandSender.mInvokeRequestBuilder.GetInvokeRequests(); @@ -1550,9 +1547,6 @@ void TestCommandInteraction::TestCommandHandlerRejectMultipleCommandsWhenHandler NL_TEST_ASSERT(apSuite, CHIP_NO_ERROR == invokeRequest.EndOfCommandDataIB()); } - NL_TEST_ASSERT(apSuite, CHIP_NO_ERROR == commandSender.mInvokeRequestBuilder.GetInvokeRequests().EndOfInvokeRequests()); - NL_TEST_ASSERT(apSuite, CHIP_NO_ERROR == commandSender.mInvokeRequestBuilder.EndOfInvokeRequestMessage()); - commandSender.MoveToState(app::CommandSender::State::AddedCommand); } @@ -1606,7 +1600,7 @@ void TestCommandInteraction::TestCommandHandlerAcceptMultipleCommands(nlTestSuit commandSender.AllocateBuffer(); - // CommandSender does not support sending multiple commands with public API, so we craft a message manaully. + // TODO(#30453): CommandSender does support sending multiple commands, update this test to use that. for (size_t i = 0; i < numberOfCommandsToSend; i++) { InvokeRequests::Builder & invokeRequests = commandSender.mInvokeRequestBuilder.GetInvokeRequests(); @@ -1627,9 +1621,6 @@ void TestCommandInteraction::TestCommandHandlerAcceptMultipleCommands(nlTestSuit NL_TEST_ASSERT(apSuite, CHIP_NO_ERROR == invokeRequest.EndOfCommandDataIB()); } - NL_TEST_ASSERT(apSuite, CHIP_NO_ERROR == commandSender.mInvokeRequestBuilder.GetInvokeRequests().EndOfInvokeRequests()); - NL_TEST_ASSERT(apSuite, CHIP_NO_ERROR == commandSender.mInvokeRequestBuilder.EndOfInvokeRequestMessage()); - commandSender.MoveToState(app::CommandSender::State::AddedCommand); } diff --git a/src/lib/core/CHIPConfig.h b/src/lib/core/CHIPConfig.h index 3fd5b497cb57b9..7cdd629d393d2c 100644 --- a/src/lib/core/CHIPConfig.h +++ b/src/lib/core/CHIPConfig.h @@ -1689,6 +1689,15 @@ extern const char CHIP_NON_PRODUCTION_MARKER[]; #define CHIP_CONFIG_MAX_ICD_CLIENTS_INFO_STORAGE_CONCURRENT_ITERATORS 1 #endif +/** + * @def CHIP_CONFIG_SENDING_BATCH_COMMANDS_ENABLED + * + * @brief Device supports sending multiple batch commands in a single Invoke Request Message. + */ +#ifndef CHIP_CONFIG_SENDING_BATCH_COMMANDS_ENABLED +#define CHIP_CONFIG_SENDING_BATCH_COMMANDS_ENABLED 0 +#endif + /** * @def CHIP_CONFIG_MAX_PATHS_PER_INVOKE * diff --git a/src/lib/core/CHIPError.h b/src/lib/core/CHIPError.h index 593e3e2d6d984d..7edc73224aad6e 100644 --- a/src/lib/core/CHIPError.h +++ b/src/lib/core/CHIPError.h @@ -1515,7 +1515,14 @@ using CHIP_ERROR = ::chip::ChipError; */ #define CHIP_ERROR_IM_MALFORMED_EVENT_DATA_IB CHIP_CORE_ERROR(0xba) -// AVAILABLE: 0xbb +/** + * @def CHIP_ERROR_MAXIMUM_PATHS_PER_INVOKE_EXCEEDED + * + * @brief + * Caller is trying to add more invoke command paths + * than what the remote node reports supporting. + */ +#define CHIP_ERROR_MAXIMUM_PATHS_PER_INVOKE_EXCEEDED CHIP_CORE_ERROR(0xbb) /** * @def CHIP_ERROR_PEER_NODE_NOT_FOUND