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
Original file line number Diff line number Diff line change
Expand Up @@ -282,8 +282,7 @@ CHIP_ERROR OTAProviderExample::SendQueryImageResponse(chip::app::CommandHandler
response.metadataForRequestor.Emplace(chip::ByteSpan());
}

ReturnErrorOnFailure(commandObj->AddResponseData(commandPath, response));

commandObj->AddResponse(commandPath, response);
return CHIP_NO_ERROR;
}

Expand Down Expand Up @@ -390,7 +389,7 @@ EmberAfStatus OTAProviderExample::HandleApplyUpdateRequest(chip::app::CommandHan
// Reset back to success case for subsequent uses
mUpdateAction = OTAApplyUpdateAction::kProceed;

VerifyOrReturnError(commandObj->AddResponseData(commandPath, response) == CHIP_NO_ERROR, EMBER_ZCL_STATUS_FAILURE);
commandObj->AddResponse(commandPath, response);

return EMBER_ZCL_STATUS_SUCCESS;
}
Expand Down
19 changes: 19 additions & 0 deletions src/app/CommandHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -481,10 +481,13 @@ CHIP_ERROR CommandHandler::AddClusterSpecificFailure(const ConcreteCommandPath &
CHIP_ERROR CommandHandler::PrepareCommand(const ConcreteCommandPath & aCommandPath, bool aStartDataStruct)
{
ReturnErrorOnFailure(AllocateBuffer());

mInvokeResponseBuilder.Checkpoint(mBackupWriter);
//
// We must not be in the middle of preparing a command, or having prepared or sent one.
//
VerifyOrReturnError(mState == State::Idle, CHIP_ERROR_INCORRECT_STATE);
MoveToState(State::Preparing);
InvokeResponseIBs::Builder & invokeResponses = mInvokeResponseBuilder.GetInvokeResponses();
InvokeResponseIB::Builder & invokeResponse = invokeResponses.CreateInvokeResponse();
ReturnErrorOnFailure(invokeResponses.GetError());
Expand Down Expand Up @@ -526,6 +529,7 @@ CHIP_ERROR CommandHandler::PrepareStatus(const ConcreteCommandPath & aCommandPat
// We must not be in the middle of preparing a command, or having prepared or sent one.
//
VerifyOrReturnError(mState == State::Idle, CHIP_ERROR_INCORRECT_STATE);
MoveToState(State::Preparing);
InvokeResponseIBs::Builder & invokeResponses = mInvokeResponseBuilder.GetInvokeResponses();
InvokeResponseIB::Builder & invokeResponse = invokeResponses.CreateInvokeResponse();
ReturnErrorOnFailure(invokeResponses.GetError());
Expand All @@ -550,6 +554,18 @@ CHIP_ERROR CommandHandler::FinishStatus()
return CHIP_NO_ERROR;
}

CHIP_ERROR CommandHandler::RollbackResponse()
{
VerifyOrReturnError(mState == State::Idle || mState == State::Preparing || mState == State::AddingCommand,
erjiaqing marked this conversation as resolved.
Show resolved Hide resolved
CHIP_ERROR_INCORRECT_STATE);
mInvokeResponseBuilder.Rollback(mBackupWriter);
mInvokeResponseBuilder.ResetError();
// Note: We only support one command per request, so we reset the state to Idle here, need to review the states when adding
// supports of having multiple requests in the same transaction.
MoveToState(State::Idle);
return CHIP_NO_ERROR;
}

TLV::TLVWriter * CommandHandler::GetCommandDataIBTLVWriter()
{
if (mState != State::AddingCommand)
Expand Down Expand Up @@ -607,6 +623,9 @@ const char * CommandHandler::GetStateStr() const
case State::Idle:
return "Idle";

case State::Preparing:
return "Preparing";

case State::AddingCommand:
return "AddingCommand";

Expand Down
66 changes: 60 additions & 6 deletions src/app/CommandHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -175,13 +175,40 @@ 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));
// TryAddResponseData will ensure we are in the correct state when calling AddResponseData.
CHIP_ERROR err = TryAddResponseData(aRequestCommandPath, aData);
if (err != CHIP_NO_ERROR)
{
// We have verified the state above, so this call must succeed since we must be in one of the states required by
erjiaqing marked this conversation as resolved.
Show resolved Hide resolved
// RollbackResponse.
RollbackResponse();
}
return err;
}

return FinishCommand(/* aEndDataStruct = */ false);
/**
* API for adding a response. This will try to encode a data response (response command), and if that fails will encode a a
* Protocols::InteractionModel::Status::Failure status response instead.
*
* 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.
*
* Since the function will call AddStatus when failed to encode the data, it cannot send any response when it failed to encode a
* status code since another AddStatus call will also fail. Users can check the log when thry failed to receive a response.
erjiaqing marked this conversation as resolved.
Show resolved Hide resolved
*
* @param [in] aRequestCommandPath the concrete path of the command we are
* responding to.
* @param [in] aData the data for the response.
*/
template <typename CommandData>
void AddResponse(const ConcreteCommandPath & aRequestCommandPath, const CommandData & aData)
{
CHIP_ERROR err = CHIP_NO_ERROR;
LogErrorOnFailure(err = AddResponseData(aRequestCommandPath, aData));
if (err != CHIP_NO_ERROR)
{
LogErrorOnFailure(AddStatus(aRequestCommandPath, Protocols::InteractionModel::Status::Failure));
bzbarsky-apple marked this conversation as resolved.
Show resolved Hide resolved
}
}

/**
Expand All @@ -207,6 +234,7 @@ class CommandHandler
enum class State
{
Idle, ///< Default state that the object starts out in, where no work has commenced
Preparing, ///< We are prepaing the command or status header.
AddingCommand, ///< In the process of adding a command.
AddedCommand, ///< A command has been completely encoded and is awaiting transmission.
CommandSent, ///< The command has been sent successfully.
Expand All @@ -216,6 +244,11 @@ class CommandHandler
void MoveToState(const State aTargetState);
const char * GetStateStr() const;

/**
* Rollback the state to before encoding the current ResponseData (before calling PrepareCommand / PrepareStatus)
*/
CHIP_ERROR RollbackResponse();

/*
* This forcibly closes the exchange context if a valid one is pointed to. Such a situation does
* not arise during normal message processing flows that all normally call Close() above. This can only
Expand Down Expand Up @@ -270,6 +303,26 @@ class CommandHandler
CHIP_ERROR AddStatusInternal(const ConcreteCommandPath & aCommandPath, const Protocols::InteractionModel::Status aStatus,
const Optional<ClusterStatus> & aClusterStatus);

/**
* If this function fails, it may leave our TLV buffer in an inconsistent state. Callers should snapshot as needed before
* calling this function, and roll back as needed afterward.
*
* @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 All @@ -280,6 +333,7 @@ class CommandHandler

State mState = State::Idle;
chip::System::PacketBufferTLVWriter mCommandMessageWriter;
TLV::TLVWriter mBackupWriter;
bool mBufferAllocated = false;
};

Expand Down
9 changes: 3 additions & 6 deletions src/app/CommandResponseHelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,9 @@ class CommandResponseHelper

CHIP_ERROR Success(const CommandData & aResponse)
{
CHIP_ERROR err = mCommandHandler->AddResponseData(mCommandPath, aResponse);
if (err == CHIP_NO_ERROR)
{
mSentResponse = true;
}
return err;
mCommandHandler->AddResponse(mCommandPath, aResponse);
mSentResponse = true;
return CHIP_NO_ERROR;
};

CHIP_ERROR Success(ClusterStatus aClusterStatus)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ void DiagnosticLogsCommandHandler::InvokeCommand(HandlerContext & handlerContext
if (mBuffer.IsEmpty())
{
response.status = chip::app::Clusters::DiagnosticLogs::LogsStatus::kNoLogs;
handlerContext.mCommandHandler.AddResponseData(handlerContext.mRequestPath, response);
handlerContext.mCommandHandler.AddResponse(handlerContext.mRequestPath, response);
break;
}

Expand All @@ -58,7 +58,7 @@ void DiagnosticLogsCommandHandler::InvokeCommand(HandlerContext & handlerContext
if (!buf)
{
response.status = chip::app::Clusters::DiagnosticLogs::LogsStatus::kBusy;
handlerContext.mCommandHandler.AddResponseData(handlerContext.mRequestPath, response);
handlerContext.mCommandHandler.AddResponse(handlerContext.mRequestPath, response);
break;
}

Expand All @@ -71,19 +71,19 @@ void DiagnosticLogsCommandHandler::InvokeCommand(HandlerContext & handlerContext
response.status = chip::app::Clusters::DiagnosticLogs::LogsStatus::kSuccess;
response.content = chip::ByteSpan(buf.get() + sizeof(timeMs), logSize - sizeof(timeMs));
response.timeStamp = timeMs;
handlerContext.mCommandHandler.AddResponseData(handlerContext.mRequestPath, response);
handlerContext.mCommandHandler.AddResponse(handlerContext.mRequestPath, response);
}
break;
case chip::app::Clusters::DiagnosticLogs::LogsIntent::kNetworkDiag: {
chip::app::Clusters::DiagnosticLogs::Commands::RetrieveLogsResponse::Type response;
response.status = chip::app::Clusters::DiagnosticLogs::LogsStatus::kNoLogs;
handlerContext.mCommandHandler.AddResponseData(handlerContext.mRequestPath, response);
handlerContext.mCommandHandler.AddResponse(handlerContext.mRequestPath, response);
}
break;
case chip::app::Clusters::DiagnosticLogs::LogsIntent::kCrashLogs: {
chip::app::Clusters::DiagnosticLogs::Commands::RetrieveLogsResponse::Type response;
response.status = chip::app::Clusters::DiagnosticLogs::LogsStatus::kNoLogs;
handlerContext.mCommandHandler.AddResponseData(handlerContext.mRequestPath, response);
handlerContext.mCommandHandler.AddResponse(handlerContext.mRequestPath, response);
}
break;
}
Expand Down
21 changes: 10 additions & 11 deletions src/app/clusters/door-lock-server/door-lock-server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2169,11 +2169,10 @@ DlStatus DoorLockServer::clearSchedules(chip::EndpointId endpointId, uint16_t us
return DlStatus::kSuccess;
}

CHIP_ERROR DoorLockServer::sendGetWeekDayScheduleResponse(chip::app::CommandHandler * commandObj,
const chip::app::ConcreteCommandPath & commandPath, uint8_t weekdayIndex,
uint16_t userIndex, DlStatus status, DlDaysMaskMap daysMask,
uint8_t startHour, uint8_t startMinute, uint8_t endHour,
uint8_t endMinute)
void DoorLockServer::sendGetWeekDayScheduleResponse(chip::app::CommandHandler * commandObj,
const chip::app::ConcreteCommandPath & commandPath, uint8_t weekdayIndex,
uint16_t userIndex, DlStatus status, DlDaysMaskMap daysMask, uint8_t startHour,
uint8_t startMinute, uint8_t endHour, uint8_t endMinute)
{
VerifyOrDie(nullptr != commandObj);

Expand All @@ -2190,7 +2189,7 @@ CHIP_ERROR DoorLockServer::sendGetWeekDayScheduleResponse(chip::app::CommandHand
response.endMinute = Optional<uint8_t>(endMinute);
}

return commandObj->AddResponseData(commandPath, response);
commandObj->AddResponse(commandPath, response);
}

bool DoorLockServer::yearDayIndexValid(chip::EndpointId endpointId, uint8_t yearDayIndex)
Expand Down Expand Up @@ -2242,10 +2241,10 @@ DlStatus DoorLockServer::clearYearDaySchedules(chip::EndpointId endpointId, uint
return DlStatus::kSuccess;
}

CHIP_ERROR DoorLockServer::sendGetYearDayScheduleResponse(chip::app::CommandHandler * commandObj,
const chip::app::ConcreteCommandPath & commandPath, uint8_t yearDayIndex,
uint16_t userIndex, DlStatus status, uint32_t localStartTime,
uint32_t localEndTime)
void DoorLockServer::sendGetYearDayScheduleResponse(chip::app::CommandHandler * commandObj,
const chip::app::ConcreteCommandPath & commandPath, uint8_t yearDayIndex,
uint16_t userIndex, DlStatus status, uint32_t localStartTime,
uint32_t localEndTime)
{
VerifyOrDie(nullptr != commandObj);

Expand All @@ -2259,7 +2258,7 @@ CHIP_ERROR DoorLockServer::sendGetYearDayScheduleResponse(chip::app::CommandHand
response.localEndTime = Optional<uint32_t>(localEndTime);
}

return commandObj->AddResponseData(commandPath, response);
commandObj->AddResponse(commandPath, response);
}

EmberAfStatus DoorLockServer::clearCredential(chip::EndpointId endpointId, chip::FabricIndex modifier, chip::NodeId sourceNodeId,
Expand Down
16 changes: 7 additions & 9 deletions src/app/clusters/door-lock-server/door-lock-server.h
Original file line number Diff line number Diff line change
Expand Up @@ -238,21 +238,19 @@ class DoorLockServer
DlStatus clearWeekDaySchedules(chip::EndpointId endpointId, uint16_t userIndex);
DlStatus clearSchedules(chip::EndpointId endpointId, uint16_t userIndex);

CHIP_ERROR sendGetWeekDayScheduleResponse(chip::app::CommandHandler * commandObj,
const chip::app::ConcreteCommandPath & commandPath, uint8_t weekdayIndex,
uint16_t userIndex, DlStatus status, DlDaysMaskMap daysMask = DlDaysMaskMap(0),
uint8_t startHour = 0, uint8_t startMinute = 0, uint8_t endHour = 0,
uint8_t endMinute = 0);
void sendGetWeekDayScheduleResponse(chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath,
uint8_t weekdayIndex, uint16_t userIndex, DlStatus status,
DlDaysMaskMap daysMask = DlDaysMaskMap(0), uint8_t startHour = 0, uint8_t startMinute = 0,
uint8_t endHour = 0, uint8_t endMinute = 0);

bool yearDayIndexValid(chip::EndpointId endpointId, uint8_t yearDayIndex);

DlStatus clearYearDaySchedule(chip::EndpointId endpointId, uint16_t userIndex, uint8_t weekDayIndex);
DlStatus clearYearDaySchedules(chip::EndpointId endpointId, uint16_t userIndex);

CHIP_ERROR sendGetYearDayScheduleResponse(chip::app::CommandHandler * commandObj,
const chip::app::ConcreteCommandPath & commandPath, uint8_t yearDayIndex,
uint16_t userIndex, DlStatus status, uint32_t localStartTime = 0,
uint32_t localEndTime = 0);
void sendGetYearDayScheduleResponse(chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath,
uint8_t yearDayIndex, uint16_t userIndex, DlStatus status, uint32_t localStartTime = 0,
uint32_t localEndTime = 0);

bool sendRemoteLockUserChange(chip::EndpointId endpointId, DlLockDataType dataType, DlDataOperationType operation,
chip::NodeId nodeId, chip::FabricIndex fabricIndex, uint16_t userIndex = 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,12 +151,12 @@ bool emberAfGeneralCommissioningClusterArmFailSafeCallback(app::CommandHandler *
CheckSuccess(failSafeContext.ArmFailSafe(accessingFabricIndex, System::Clock::Seconds16(commandData.expiryLengthSeconds)),
Failure);
response.errorCode = CommissioningError::kOk;
CheckSuccess(commandObj->AddResponseData(commandPath, response), Failure);
commandObj->AddResponse(commandPath, response);
}
else
{
response.errorCode = CommissioningError::kBusyWithOtherAdmin;
CheckSuccess(commandObj->AddResponseData(commandPath, response), Failure);
commandObj->AddResponse(commandPath, response);
}

return true;
Expand Down Expand Up @@ -201,7 +201,7 @@ bool emberAfGeneralCommissioningClusterCommissioningCompleteCallback(
}
}

CheckSuccess(commandObj->AddResponseData(commandPath, response), Failure);
commandObj->AddResponse(commandPath, response);

return true;
}
Expand All @@ -218,7 +218,7 @@ bool emberAfGeneralCommissioningClusterSetRegulatoryConfigCallback(app::CommandH

Commands::SetRegulatoryConfigResponse::Type response;
response.errorCode = CommissioningError::kOk;
CheckSuccess(commandObj->AddResponseData(commandPath, response), Failure);
commandObj->AddResponse(commandPath, response);

return true;
}
Expand Down
13 changes: 2 additions & 11 deletions src/app/clusters/group-key-mgmt-server/group-key-mgmt-server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -438,12 +438,7 @@ bool emberAfGroupKeyManagementClusterKeySetReadCallback(
}
response.groupKeySet.epochKey2.SetNull();

CHIP_ERROR err = commandObj->AddResponseData(commandPath, response);
if (CHIP_NO_ERROR != err)
{
ChipLogDetail(Zcl, "GroupKeyManagementCluster: KeySetRead failed: %s", ErrorStr(err));
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_FAILURE);
}
commandObj->AddResponse(commandPath, response);
return true;
}

Expand Down Expand Up @@ -530,11 +525,7 @@ bool emberAfGroupKeyManagementClusterKeySetReadAllIndicesCallback(
return true;
}

CHIP_ERROR err = commandObj->AddResponseData(commandPath, KeySetReadAllIndicesResponse(keysIt));
if (CHIP_NO_ERROR != err)
{
ChipLogDetail(Zcl, "GroupKeyManagementCluster: KeySetReadAllIndices failed: %s", ErrorStr(err));
}
commandObj->AddResponse(commandPath, KeySetReadAllIndicesResponse(keysIt));
keysIt->Release();
return true;
}
Loading