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

Implement the server side of timed invoke. #12389

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/examples-nrfconnect.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,14 @@ jobs:
run: |
scripts/run_in_build_env.sh "scripts/tests/nrfconnect_native_posix_tests.sh native_posix_64"

- name: Uploading Failed Test Logs
uses: actions/upload-artifact@v2
if: ${{ failure() }} && ${{ !env.ACT }}
with:
name: test-log
path: |
src/test_driver/nrfconnect/build/Testing/Temporary/LastTest.log

- name: Uploading Size Reports
uses: actions/upload-artifact@v2
if: ${{ !env.ACT }}
Expand Down
1 change: 1 addition & 0 deletions examples/all-clusters-app/ameba/chip_main.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ list(
${chip_dir}/src/app/EventManagement.cpp
${chip_dir}/src/app/ReadClient.cpp
${chip_dir}/src/app/ReadHandler.cpp
${chip_dir}/src/app/TimedHandler.cpp
${chip_dir}/src/app/WriteClient.cpp
${chip_dir}/src/app/WriteHandler.cpp
${chip_dir}/src/app/util/CHIPDeviceCallbacksMgr.cpp
Expand Down
1 change: 1 addition & 0 deletions examples/lighting-app/ameba/chip_main.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ list(
${chip_dir}/src/app/EventManagement.cpp
${chip_dir}/src/app/ReadClient.cpp
${chip_dir}/src/app/ReadHandler.cpp
${chip_dir}/src/app/TimedHandler.cpp
${chip_dir}/src/app/WriteClient.cpp
${chip_dir}/src/app/WriteHandler.cpp
${chip_dir}/src/app/util/CHIPDeviceCallbacksMgr.cpp
Expand Down
2 changes: 1 addition & 1 deletion scripts/tests/nrfconnect_native_posix_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ env
cd "$CHIP_ROOT/src/test_driver/nrfconnect" &&
west build -b "$BOARD" &&
cd build &&
timeout 5m ctest -V
timeout 5m ctest -V --output-on-failure
2 changes: 2 additions & 0 deletions src/app/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ static_library("app") {
"ReadHandler.cpp",
"StatusResponse.cpp",
"StatusResponse.h",
"TimedHandler.cpp",
"TimedHandler.h",
"WriteClient.cpp",
"WriteHandler.cpp",
"decoder.cpp",
Expand Down
33 changes: 30 additions & 3 deletions src/app/CommandHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ CHIP_ERROR CommandHandler::AllocateBuffer()
}

CHIP_ERROR CommandHandler::OnInvokeCommandRequest(Messaging::ExchangeContext * ec, const PayloadHeader & payloadHeader,
System::PacketBufferHandle && payload)
System::PacketBufferHandle && payload, bool isTimedInvoke)
{
System::PacketBufferHandle response;
VerifyOrReturnError(mState == CommandState::Idle, CHIP_ERROR_INCORRECT_STATE);
Expand All @@ -73,14 +73,18 @@ CHIP_ERROR CommandHandler::OnInvokeCommandRequest(Messaging::ExchangeContext * e
mpExchangeCtx = ec;

// Use the RAII feature, if this is the only Handle when this function returns, DecrementHoldOff will trigger sending response.
// TODO: This is broken! If something under here returns error, we will try
// to SendCommandResponse(), and then our caller will try to send a status
// response too. Figure out at what point it's our responsibility to
// handler errors vs our caller's.
Handle workHandle(this);
mpExchangeCtx->WillSendMessage();
ReturnErrorOnFailure(ProcessInvokeRequest(std::move(payload)));
ReturnErrorOnFailure(ProcessInvokeRequest(std::move(payload), isTimedInvoke));

return CHIP_NO_ERROR;
}

CHIP_ERROR CommandHandler::ProcessInvokeRequest(System::PacketBufferHandle && payload)
CHIP_ERROR CommandHandler::ProcessInvokeRequest(System::PacketBufferHandle && payload, bool isTimedInvoke)
{
CHIP_ERROR err = CHIP_NO_ERROR;
System::PacketBufferTLVReader reader;
Expand All @@ -97,6 +101,29 @@ CHIP_ERROR CommandHandler::ProcessInvokeRequest(System::PacketBufferHandle && pa
ReturnErrorOnFailure(invokeRequestMessage.GetTimedRequest(&mTimedRequest));
ReturnErrorOnFailure(invokeRequestMessage.GetInvokeRequests(&invokeRequests));

if (mTimedRequest != isTimedInvoke)
{
// The message thinks it should be part of a timed interaction but it's
// not, or vice versa. Spec says to Respond with UNSUPPORTED_ACCESS.
err = StatusResponse::SendStatusResponse(Protocols::InteractionModel::Status::UnsupportedAccess, mpExchangeCtx,
/* aExpectResponse = */ false);

// Some unit tests call this function in an abnormal state when we don't
// even have an exchange.
if (err != CHIP_NO_ERROR && mpExchangeCtx)
{
// We have to manually close the exchange, because we called
// WillSendMessage already.
mpExchangeCtx->Close();
}

// Null out the (now-closed) exchange, so that when we try to
// SendCommandResponse() later (when our holdoff count drops to 0) it
// just fails and we don't double-respond.
mpExchangeCtx = nullptr;
return err;
}

invokeRequests.GetReader(&invokeRequestsReader);
while (CHIP_NO_ERROR == (err = invokeRequestsReader.Next()))
{
Expand Down
13 changes: 11 additions & 2 deletions src/app/CommandHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,16 +128,20 @@ class CommandHandler : public Command
*
* This function will always call the OnDone function above on the registered callback
* before returning.
*
* isTimedInvoke is true if and only if this is part of a Timed Invoke
* transaction (i.e. was preceded by a Timed Request). If we reach here,
* the timer verification has already been done.
*/
CHIP_ERROR OnInvokeCommandRequest(Messaging::ExchangeContext * ec, const PayloadHeader & payloadHeader,
System::PacketBufferHandle && payload);
System::PacketBufferHandle && payload, bool isTimedInvoke);
CHIP_ERROR AddStatus(const ConcreteCommandPath & aCommandPath, const Protocols::InteractionModel::Status aStatus) override;

CHIP_ERROR AddClusterSpecificSuccess(const ConcreteCommandPath & aCommandPath, ClusterStatus aClusterStatus) override;

CHIP_ERROR AddClusterSpecificFailure(const ConcreteCommandPath & aCommandPath, ClusterStatus aClusterStatus) override;

CHIP_ERROR ProcessInvokeRequest(System::PacketBufferHandle && payload);
CHIP_ERROR ProcessInvokeRequest(System::PacketBufferHandle && payload, bool isTimedInvoke);
CHIP_ERROR PrepareCommand(const CommandPathParams & aCommandPathParams, bool aStartDataStruct = true);
CHIP_ERROR FinishCommand(bool aStartDataStruct = true);
CHIP_ERROR PrepareStatus(const CommandPathParams & aCommandPathParams);
Expand Down Expand Up @@ -166,6 +170,11 @@ class CommandHandler : public Command
return FinishCommand(/* aEndDataStruct = */ false);
}

/**
* Check whether the InvokeRequest we are handling is a timed invoke.
*/
bool IsTimedInvoke() const { return mTimedRequest; }

private:
friend class TestCommandInteraction;
friend class CommandHandler::Handle;
Expand Down
71 changes: 68 additions & 3 deletions src/app/InteractionModelEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,15 @@ void InteractionModelEngine::Shutdown()
return true;
});

mTimedHandlers.ForEachActiveObject([this](TimedHandler * obj) -> bool {
// This calls back into us and deallocates |obj|. As above, this is not
// really guaranteed, and we should do something better here (like
// ignoring the calls to OnTimedInteractionFailed and then doing a
// DeallocateAll.
mpExchangeMgr->CloseAllContextsForDelegate(obj);
return true;
});

for (auto & readClient : mReadClients)
{
if (!readClient.IsFree())
Expand Down Expand Up @@ -277,7 +286,7 @@ void InteractionModelEngine::OnDone(CommandHandler & apCommandObj)

CHIP_ERROR InteractionModelEngine::OnInvokeCommandRequest(Messaging::ExchangeContext * apExchangeContext,
const PayloadHeader & aPayloadHeader,
System::PacketBufferHandle && aPayload,
System::PacketBufferHandle && aPayload, bool aIsTimedInvoke,
Protocols::InteractionModel::Status & aStatus)
{
CommandHandler * commandHandler = mCommandHandlerObjs.CreateObject(this);
Expand All @@ -287,7 +296,8 @@ CHIP_ERROR InteractionModelEngine::OnInvokeCommandRequest(Messaging::ExchangeCon
aStatus = Protocols::InteractionModel::Status::Busy;
return CHIP_ERROR_NO_MEMORY;
}
ReturnErrorOnFailure(commandHandler->OnInvokeCommandRequest(apExchangeContext, aPayloadHeader, std::move(aPayload)));
ReturnErrorOnFailure(
commandHandler->OnInvokeCommandRequest(apExchangeContext, aPayloadHeader, std::move(aPayload), aIsTimedInvoke));
aStatus = Protocols::InteractionModel::Status::Success;
return CHIP_NO_ERROR;
}
Expand Down Expand Up @@ -360,6 +370,25 @@ CHIP_ERROR InteractionModelEngine::OnWriteRequest(Messaging::ExchangeContext * a
return CHIP_NO_ERROR;
}

CHIP_ERROR InteractionModelEngine::OnTimedRequest(Messaging::ExchangeContext * apExchangeContext,
const PayloadHeader & aPayloadHeader, System::PacketBufferHandle && aPayload,
Protocols::InteractionModel::Status & aStatus)
{
TimedHandler * handler = mTimedHandlers.CreateObject();
if (handler == nullptr)
{
ChipLogProgress(InteractionModel, "no resource for Timed interaction");
aStatus = Protocols::InteractionModel::Status::Busy;
return CHIP_ERROR_NO_MEMORY;
}

// The timed handler takes over handling of this exchange and will do its
// own status reporting as needed.
aStatus = Protocols::InteractionModel::Status::Success;
apExchangeContext->SetDelegate(handler);
return handler->OnMessageReceived(apExchangeContext, aPayloadHeader, std::move(aPayload));
}

CHIP_ERROR InteractionModelEngine::OnUnsolicitedReportData(Messaging::ExchangeContext * apExchangeContext,
const PayloadHeader & aPayloadHeader,
System::PacketBufferHandle && aPayload)
Expand Down Expand Up @@ -392,12 +421,15 @@ CHIP_ERROR InteractionModelEngine::OnUnsolicitedReportData(Messaging::ExchangeCo
CHIP_ERROR InteractionModelEngine::OnMessageReceived(Messaging::ExchangeContext * apExchangeContext,
const PayloadHeader & aPayloadHeader, System::PacketBufferHandle && aPayload)
{
using namespace Protocols::InteractionModel;

CHIP_ERROR err = CHIP_NO_ERROR;
Protocols::InteractionModel::Status status = Protocols::InteractionModel::Status::Failure;

if (aPayloadHeader.HasMessageType(Protocols::InteractionModel::MsgType::InvokeCommandRequest))
{
SuccessOrExit(OnInvokeCommandRequest(apExchangeContext, aPayloadHeader, std::move(aPayload), status));
SuccessOrExit(
OnInvokeCommandRequest(apExchangeContext, aPayloadHeader, std::move(aPayload), /* aIsTimedInvoke = */ false, status));
}
else if (aPayloadHeader.HasMessageType(Protocols::InteractionModel::MsgType::ReadRequest))
{
Expand All @@ -418,6 +450,10 @@ CHIP_ERROR InteractionModelEngine::OnMessageReceived(Messaging::ExchangeContext
ReturnErrorOnFailure(OnUnsolicitedReportData(apExchangeContext, aPayloadHeader, std::move(aPayload)));
status = Protocols::InteractionModel::Status::Success;
}
else if (aPayloadHeader.HasMessageType(MsgType::TimedRequest))
{
SuccessOrExit(OnTimedRequest(apExchangeContext, aPayloadHeader, std::move(aPayload), status));
}
else
{
ChipLogProgress(InteractionModel, "Msg type %d not supported", aPayloadHeader.GetMessageType());
Expand Down Expand Up @@ -550,6 +586,9 @@ void InteractionModelEngine::DispatchCommand(CommandHandler & apCommandObj, cons

if (handler)
{
// TODO: Figure out who is responsible for handling checking
// apCommandObj->IsTimedInvoke() for commands that require a timed
// invoke and have a CommandHandlerInterface handling them.
CommandHandlerInterface::HandlerContext context(apCommandObj, aCommandPath, apPayload);
handler->InvokeCommand(context);

Expand Down Expand Up @@ -657,5 +696,31 @@ CommandHandlerInterface * InteractionModelEngine::FindCommandHandler(EndpointId
return nullptr;
}

void InteractionModelEngine::OnTimedInteractionFailed(TimedHandler * apTimedHandler)
{
mTimedHandlers.Deallocate(apTimedHandler);
}

void InteractionModelEngine::OnTimedInvoke(TimedHandler * apTimedHandler, Messaging::ExchangeContext * apExchangeContext,
const PayloadHeader & aPayloadHeader, System::PacketBufferHandle && aPayload)
{
using namespace Protocols::InteractionModel;

// Reset the ourselves as the exchange delegate for now, to match what we'd
// do with an initial unsolicited invoke.
apExchangeContext->SetDelegate(this);
mTimedHandlers.Deallocate(apTimedHandler);

VerifyOrDie(aPayloadHeader.HasMessageType(MsgType::InvokeCommandRequest));
VerifyOrDie(!apExchangeContext->IsGroupExchangeContext());

Status status = Status::Failure;
OnInvokeCommandRequest(apExchangeContext, aPayloadHeader, std::move(aPayload), /* aIsTimedInvoke = */ true, status);
if (status != Status::Success)
{
StatusResponse::SendStatusResponse(status, apExchangeContext, /* aExpectResponse = */ false);
}
}

} // namespace app
} // namespace chip
27 changes: 26 additions & 1 deletion src/app/InteractionModelEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
#include <app/ReadClient.h>
#include <app/ReadHandler.h>
#include <app/StatusResponse.h>
#include <app/TimedHandler.h>
#include <app/WriteClient.h>
#include <app/WriteHandler.h>
#include <app/reporting/Engine.h>
Expand Down Expand Up @@ -194,6 +195,20 @@ class InteractionModelEngine : public Messaging::ExchangeDelegate, public Comman
CommandHandlerInterface * FindCommandHandler(EndpointId endpointId, ClusterId clusterId);
void UnregisterCommandHandlers(EndpointId endpointId);

/**
* Called when a timed interaction has failed (i.e. the exchange it was
* happening on has closed while the exchange delegate was the timed
* handler).
*/
void OnTimedInteractionFailed(TimedHandler * apTimedHandler);

/**
* Called when a timed invoke is received. This function takes over all
* handling of the exchange, status reporting, and so forth.
*/
void OnTimedInvoke(TimedHandler * apTimedHandler, Messaging::ExchangeContext * apExchangeContext,
const PayloadHeader & aPayloadHeader, System::PacketBufferHandle && aPayload);

private:
friend class reporting::Engine;
friend class TestCommandInteraction;
Expand All @@ -206,7 +221,8 @@ class InteractionModelEngine : public Messaging::ExchangeDelegate, public Comman
* expected to set it to success if it does not want an automatic status response message to be sent.
*/
CHIP_ERROR OnInvokeCommandRequest(Messaging::ExchangeContext * apExchangeContext, const PayloadHeader & aPayloadHeader,
System::PacketBufferHandle && aPayload, Protocols::InteractionModel::Status & aStatus);
System::PacketBufferHandle && aPayload, bool aIsTimedInvoke,
Protocols::InteractionModel::Status & aStatus);
CHIP_ERROR OnMessageReceived(Messaging::ExchangeContext * apExchangeContext, const PayloadHeader & aPayloadHeader,
System::PacketBufferHandle && aPayload) override;
void OnResponseTimeout(Messaging::ExchangeContext * ec) override;
Expand All @@ -229,6 +245,14 @@ class InteractionModelEngine : public Messaging::ExchangeDelegate, public Comman
CHIP_ERROR OnWriteRequest(Messaging::ExchangeContext * apExchangeContext, const PayloadHeader & aPayloadHeader,
System::PacketBufferHandle && aPayload, Protocols::InteractionModel::Status & aStatus);

/**
* Called when Interaction Model receives a Timed Request message. Errors processing
* the Timed Request are handled entirely within this function. The caller pre-sets status to failure and the callee is
* expected to set it to success if it does not want an automatic status response message to be sent.
*/
CHIP_ERROR OnTimedRequest(Messaging::ExchangeContext * apExchangeContext, const PayloadHeader & aPayloadHeader,
System::PacketBufferHandle && aPayload, Protocols::InteractionModel::Status & aStatus);

/**This function handles processing of un-solicited ReportData messages on the client, which can
* only occur post subscription establishment
*/
Expand All @@ -247,6 +271,7 @@ class InteractionModelEngine : public Messaging::ExchangeDelegate, public Comman
// TODO(#8006): investgate if we can disable some IM functions on some compact accessories.
// TODO(#8006): investgate if we can provide more flexible object management on devices with more resources.
BitMapObjectPool<CommandHandler, CHIP_IM_MAX_NUM_COMMAND_HANDLER> mCommandHandlerObjs;
BitMapObjectPool<TimedHandler, CHIP_IM_MAX_NUM_TIMED_HANDLER> mTimedHandlers;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One of more objects from this pool outlives the InteractionModelEngine in a TestTimedHandler unit test. What should happen?

ReadClient mReadClients[CHIP_IM_MAX_NUM_READ_CLIENT];
ReadHandler mReadHandlers[CHIP_IM_MAX_NUM_READ_HANDLER];
WriteClient mWriteClients[CHIP_IM_MAX_NUM_WRITE_CLIENT];
Expand Down
2 changes: 1 addition & 1 deletion src/app/MessageDef/InvokeRequestMessage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ CHIP_ERROR InvokeRequestMessage::Parser::GetSuppressResponse(bool * const apSupp

CHIP_ERROR InvokeRequestMessage::Parser::GetTimedRequest(bool * const apTimedRequest) const
{
return GetSimpleValue(to_underlying(Tag::kSuppressResponse), TLV::kTLVType_Boolean, apTimedRequest);
return GetSimpleValue(to_underlying(Tag::kTimedRequest), TLV::kTLVType_Boolean, apTimedRequest);
}

CHIP_ERROR InvokeRequestMessage::Parser::GetInvokeRequests(InvokeRequests::Parser * const apInvokeRequests) const
Expand Down
Loading