Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[IM] Make CommandHandler::AddResponseData atomic #15906

Merged
merged 12 commits into from
Mar 29, 2022
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()
erjiaqing marked this conversation as resolved.
Show resolved Hide resolved
{
VerifyOrReturnError(mState == State::Idle || mState == State::AddedCommand || mState == State::AddingCommand,
erjiaqing marked this conversation as resolved.
Show resolved Hide resolved
erjiaqing marked this conversation as resolved.
Show resolved Hide resolved
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);
erjiaqing marked this conversation as resolved.
Show resolved Hide resolved

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.
erjiaqing marked this conversation as resolved.
Show resolved Hide resolved
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
erjiaqing marked this conversation as resolved.
Show resolved Hide resolved
* 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
82 changes: 82 additions & 0 deletions src/app/tests/TestCommandInteraction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

#include <app/AppBuildConfig.h>
#include <app/InteractionModelEngine.h>
#include <app/data-model/Encode.h>
#include <app/tests/AppTestContext.h>
#include <lib/core/CHIPCore.h>
#include <lib/core/CHIPTLV.h>
Expand Down Expand Up @@ -178,6 +179,8 @@ class TestCommandInteraction
static void TestCommandHandlerWithProcessReceivedNotExistCommand(nlTestSuite * apSuite, void * apContext);
static void TestCommandHandlerWithSendSimpleCommandData(nlTestSuite * apSuite, void * apContext);
static void TestCommandHandlerCommandDataEncoding(nlTestSuite * apSuite, void * apContext);
static void TestCommandHandlerCommandEncodeFailure(nlTestSuite * apSuite, void * apContext);
static void TestCommandHandlerCommandEncodeExternalFailure(nlTestSuite * apSuite, void * apContext);
static void TestCommandHandlerWithSendSimpleStatusCode(nlTestSuite * apSuite, void * apContext);
static void TestCommandHandlerWithSendEmptyResponse(nlTestSuite * apSuite, void * apContext);
static void TestCommandHandlerWithProcessReceivedMsg(nlTestSuite * apSuite, void * apContext);
Expand Down Expand Up @@ -514,6 +517,23 @@ struct Fields
}
};

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);
}
};

void TestCommandInteraction::TestCommandHandlerCommandDataEncoding(nlTestSuite * apSuite, void * apContext)
{
TestContext & ctx = *static_cast<TestContext *>(apContext);
Expand Down Expand Up @@ -542,6 +562,66 @@ void TestCommandInteraction::TestCommandHandlerCommandDataEncoding(nlTestSuite *
#endif
}

void TestCommandInteraction::TestCommandHandlerCommandEncodeFailure(nlTestSuite * apSuite, void * apContext)
{
TestContext & ctx = *static_cast<TestContext *>(apContext);
CHIP_ERROR err = CHIP_NO_ERROR;
app::CommandHandler commandHandler(nullptr);
System::PacketBufferHandle commandPacket;

TestExchangeDelegate delegate;
commandHandler.mpExchangeCtx = ctx.NewExchangeToAlice(&delegate);

auto path = MakeTestCommandPath();

err = commandHandler.AddResponseDataOrFailureStatus(ConcreteCommandPath(path.mEndpointId, path.mClusterId, path.mCommandId),
BadFields());
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
err = commandHandler.Finalize(commandPacket);
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);

#if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK
chip::System::PacketBufferTLVReader reader;
InvokeResponseMessage::Parser invokeResponseMessageParser;
reader.Init(std::move(commandPacket));
err = invokeResponseMessageParser.Init(reader);
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
err = invokeResponseMessageParser.CheckSchemaValidity();
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
#endif
}

void TestCommandInteraction::TestCommandHandlerCommandEncodeExternalFailure(nlTestSuite * apSuite, void * apContext)
{
TestContext & ctx = *static_cast<TestContext *>(apContext);
CHIP_ERROR err = CHIP_NO_ERROR;
app::CommandHandler commandHandler(nullptr);
System::PacketBufferHandle commandPacket;

TestExchangeDelegate delegate;
commandHandler.mpExchangeCtx = ctx.NewExchangeToAlice(&delegate);

auto path = MakeTestCommandPath();

err = commandHandler.AddResponseData(ConcreteCommandPath(path.mEndpointId, path.mClusterId, path.mCommandId), BadFields());
NL_TEST_ASSERT(apSuite, err != CHIP_NO_ERROR);
err = commandHandler.AddStatus(ConcreteCommandPath(path.mEndpointId, path.mClusterId, path.mCommandId),
Protocols::InteractionModel::Status::Failure);
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
err = commandHandler.Finalize(commandPacket);
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);

#if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK
chip::System::PacketBufferTLVReader reader;
InvokeResponseMessage::Parser invokeResponseMessageParser;
reader.Init(std::move(commandPacket));
err = invokeResponseMessageParser.Init(reader);
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
err = invokeResponseMessageParser.CheckSchemaValidity();
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
#endif
}

void TestCommandInteraction::TestCommandHandlerWithSendSimpleStatusCode(nlTestSuite * apSuite, void * apContext)
{
// Send response which has simple status code and command path
Expand Down Expand Up @@ -768,6 +848,8 @@ const nlTest sTests[] =
NL_TEST_DEF("TestCommandSenderWithProcessReceivedMsg", chip::app::TestCommandInteraction::TestCommandSenderWithProcessReceivedMsg),
NL_TEST_DEF("TestCommandHandlerWithSendSimpleCommandData", chip::app::TestCommandInteraction::TestCommandHandlerWithSendSimpleCommandData),
NL_TEST_DEF("TestCommandHandlerCommandDataEncoding", chip::app::TestCommandInteraction::TestCommandHandlerCommandDataEncoding),
NL_TEST_DEF("TestCommandHandlerCommandEncodeFailure", chip::app::TestCommandInteraction::TestCommandHandlerCommandEncodeFailure),
NL_TEST_DEF("TestCommandHandlerCommandEncodeExternalFailure", chip::app::TestCommandInteraction::TestCommandHandlerCommandEncodeExternalFailure),
NL_TEST_DEF("TestCommandHandlerWithSendSimpleStatusCode", chip::app::TestCommandInteraction::TestCommandHandlerWithSendSimpleStatusCode),
NL_TEST_DEF("TestCommandHandlerWithProcessReceivedMsg", chip::app::TestCommandInteraction::TestCommandHandlerWithProcessReceivedMsg),
NL_TEST_DEF("TestCommandHandlerWithProcessReceivedNotExistCommand", chip::app::TestCommandInteraction::TestCommandHandlerWithProcessReceivedNotExistCommand),
Expand Down