Skip to content

Commit

Permalink
[IM] Make CommandHandler::AddResponseData atomic
Browse files Browse the repository at this point in the history
  • Loading branch information
erjiaqing committed Mar 7, 2022
1 parent 5202292 commit da77cb0
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 6 deletions.
11 changes: 11 additions & 0 deletions src/app/CommandHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,17 @@ CHIP_ERROR CommandHandler::FinishStatus()
return CHIP_NO_ERROR;
}

CHIP_ERROR CommandHandler::ResetResponse()
{
VerifyOrReturnError(mState == State::Idle || mState == State::AddedCommand || mState == State::AddingCommand,
CHIP_ERROR_INCORRECT_STATE);
// Calling mCommandMessageWriter will release its underlying buffer, thus we can allocate another one when encode something.
mCommandMessageWriter.Reset();
mBufferAllocated = false;
MoveToState(State::Idle);
return CHIP_NO_ERROR;
}

TLV::TLVWriter * CommandHandler::GetCommandDataIBTLVWriter()
{
if (mState != State::AddingCommand)
Expand Down
67 changes: 61 additions & 6 deletions src/app/CommandHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,15 @@ class CommandHandler
TLV::TLVWriter * GetCommandDataIBTLVWriter();
FabricIndex GetAccessingFabricIndex() const;

/**
* API for resetting the internal response builder, useful when encoding the response manually.
* The TLVWriter got from GetCommandDataIBTLVWriter will be invalid after calling this.
*
* After calling this, users must call PrepareCommand or PrepareStatus before encoding something else. AddResponseData and
* AddStatus will handle this correctly.
*/
CHIP_ERROR ResetResponse();

/**
* API for adding a data response. The template parameter T is generally
* expected to be a ClusterName::Commands::CommandName::Type struct, but any
Expand All @@ -175,13 +184,37 @@ class CommandHandler
template <typename CommandData>
CHIP_ERROR AddResponseData(const ConcreteCommandPath & aRequestCommandPath, const CommandData & aData)
{
ConcreteCommandPath path = { aRequestCommandPath.mEndpointId, aRequestCommandPath.mClusterId, CommandData::GetCommandId() };
ReturnErrorOnFailure(PrepareCommand(path, false));
TLV::TLVWriter * writer = GetCommandDataIBTLVWriter();
VerifyOrReturnError(writer != nullptr, CHIP_ERROR_INCORRECT_STATE);
ReturnErrorOnFailure(DataModel::Encode(*writer, TLV::ContextTag(to_underlying(CommandDataIB::Tag::kData)), aData));
// We should not encode anything when we are not in the correct state, the user must be using CommandHandler incorrectly.
VerifyOrReturnError(mState == State::Idle, CHIP_ERROR_INCORRECT_STATE);

return FinishCommand(/* aEndDataStruct = */ false);
CHIP_ERROR err = TryAddResponseData(aRequestCommandPath, aData);
if (err != CHIP_NO_ERROR)
{
// We have verified the state above, so this call must success since we must be one of the state required by
// ResetResponse.
ResetResponse();
}
return err;
}

/**
* API for adding a data response. Will encode Protocols::InteractionModel::Status::Failure status code when it failed to encode
* the command data. The template parameter T is generally expected to be a ClusterName::Commands::CommandName::Type struct,
* but any object that can be encoded using the DataModel::Encode machinery and exposes the right command id will work.
*
* @param [in] aRequestCommandPath the concrete path of the command we are
* responding to.
* @param [in] aData the data for the response.
*/
template <typename CommandData>
CHIP_ERROR AddResponseDataOrFailureStatus(const ConcreteCommandPath & aRequestCommandPath, const CommandData & aData)
{
CHIP_ERROR err = AddResponseData(aRequestCommandPath, aData);
if (err != CHIP_NO_ERROR)
{
return AddStatus(aRequestCommandPath, Protocols::InteractionModel::Status::Failure);
}
return err;
}

/**
Expand Down Expand Up @@ -270,6 +303,28 @@ class CommandHandler
CHIP_ERROR AddStatusInternal(const ConcreteCommandPath & aCommandPath, const Protocols::InteractionModel::Status aStatus,
const Optional<ClusterStatus> & aClusterStatus);

/**
* Adds a data response. The template parameter T is generally
* expected to be a ClusterName::Commands::CommandName::Type struct, but any
* object that can be encoded using the DataModel::Encode machinery and
* exposes the right command id will work.
*
* @param [in] aRequestCommandPath the concrete path of the command we are
* responding to.
* @param [in] aData the data for the response.
*/
template <typename CommandData>
CHIP_ERROR TryAddResponseData(const ConcreteCommandPath & aRequestCommandPath, const CommandData & aData)
{
ConcreteCommandPath path = { aRequestCommandPath.mEndpointId, aRequestCommandPath.mClusterId, CommandData::GetCommandId() };
ReturnErrorOnFailure(PrepareCommand(path, false));
TLV::TLVWriter * writer = GetCommandDataIBTLVWriter();
VerifyOrReturnError(writer != nullptr, CHIP_ERROR_INCORRECT_STATE);
ReturnErrorOnFailure(DataModel::Encode(*writer, TLV::ContextTag(to_underlying(CommandDataIB::Tag::kData)), aData));

return FinishCommand(/* aEndDataStruct = */ false);
}

Messaging::ExchangeContext * mpExchangeCtx = nullptr;
Callback * mpCallback = nullptr;
InvokeResponseMessage::Builder mInvokeResponseBuilder;
Expand Down

0 comments on commit da77cb0

Please sign in to comment.