From 89a94910f23b1bd5c42bd7fe8d40ea2f9d1228c6 Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Wed, 1 Dec 2021 14:34:57 -0500 Subject: [PATCH] Implement the server side of timed invoke. (#12389) --- .github/workflows/examples-nrfconnect.yaml | 8 + .../all-clusters-app/ameba/chip_main.cmake | 1 + examples/lighting-app/ameba/chip_main.cmake | 1 + .../tests/nrfconnect_native_posix_tests.sh | 2 +- src/app/BUILD.gn | 2 + src/app/CommandHandler.cpp | 33 ++- src/app/CommandHandler.h | 13 +- src/app/InteractionModelEngine.cpp | 71 +++++- src/app/InteractionModelEngine.h | 27 +- src/app/MessageDef/InvokeRequestMessage.cpp | 2 +- src/app/TimedHandler.cpp | 132 ++++++++++ src/app/TimedHandler.h | 99 ++++++++ src/app/WriteHandler.h | 5 + src/app/tests/AppTestContext.cpp | 1 + src/app/tests/BUILD.gn | 1 + src/app/tests/TestCommandInteraction.cpp | 52 ++-- src/app/tests/TestTimedHandler.cpp | 240 ++++++++++++++++++ .../util/ember-compatibility-functions.cpp | 5 + .../im_command_handler_cluster_commands.zapt | 6 + src/lib/core/CHIPConfig.h | 11 + src/messaging/ExchangeMgr.cpp | 3 + .../zap-generated/IMClusterCommandHandler.cpp | 5 + 22 files changed, 690 insertions(+), 30 deletions(-) create mode 100644 src/app/TimedHandler.cpp create mode 100644 src/app/TimedHandler.h create mode 100644 src/app/tests/TestTimedHandler.cpp diff --git a/.github/workflows/examples-nrfconnect.yaml b/.github/workflows/examples-nrfconnect.yaml index 63bd1c210c6238..763299ef0e9006 100644 --- a/.github/workflows/examples-nrfconnect.yaml +++ b/.github/workflows/examples-nrfconnect.yaml @@ -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 }} diff --git a/examples/all-clusters-app/ameba/chip_main.cmake b/examples/all-clusters-app/ameba/chip_main.cmake index 6b0541edb6b76b..bdf74e801590ce 100755 --- a/examples/all-clusters-app/ameba/chip_main.cmake +++ b/examples/all-clusters-app/ameba/chip_main.cmake @@ -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 diff --git a/examples/lighting-app/ameba/chip_main.cmake b/examples/lighting-app/ameba/chip_main.cmake index f692189a93d7c4..007b524c4ab0f2 100755 --- a/examples/lighting-app/ameba/chip_main.cmake +++ b/examples/lighting-app/ameba/chip_main.cmake @@ -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 diff --git a/scripts/tests/nrfconnect_native_posix_tests.sh b/scripts/tests/nrfconnect_native_posix_tests.sh index 559d2264358eb0..174bd059bcc487 100755 --- a/scripts/tests/nrfconnect_native_posix_tests.sh +++ b/scripts/tests/nrfconnect_native_posix_tests.sh @@ -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 diff --git a/src/app/BUILD.gn b/src/app/BUILD.gn index 64d255211680f4..545c53241f1281 100644 --- a/src/app/BUILD.gn +++ b/src/app/BUILD.gn @@ -120,6 +120,8 @@ static_library("app") { "ReadHandler.cpp", "StatusResponse.cpp", "StatusResponse.h", + "TimedHandler.cpp", + "TimedHandler.h", "WriteClient.cpp", "WriteHandler.cpp", "decoder.cpp", diff --git a/src/app/CommandHandler.cpp b/src/app/CommandHandler.cpp index e21a1462d2feee..78aaed63c0ca98 100644 --- a/src/app/CommandHandler.cpp +++ b/src/app/CommandHandler.cpp @@ -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); @@ -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; @@ -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())) { diff --git a/src/app/CommandHandler.h b/src/app/CommandHandler.h index 85a5573cae452a..29cc3b62b80896 100644 --- a/src/app/CommandHandler.h +++ b/src/app/CommandHandler.h @@ -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); @@ -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; diff --git a/src/app/InteractionModelEngine.cpp b/src/app/InteractionModelEngine.cpp index 2546c646e38b62..f9655ea0ee104e 100644 --- a/src/app/InteractionModelEngine.cpp +++ b/src/app/InteractionModelEngine.cpp @@ -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()) @@ -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); @@ -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; } @@ -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) @@ -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)) { @@ -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()); @@ -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); @@ -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 diff --git a/src/app/InteractionModelEngine.h b/src/app/InteractionModelEngine.h index 7aa33a5400ddeb..beeca955ab6485 100644 --- a/src/app/InteractionModelEngine.h +++ b/src/app/InteractionModelEngine.h @@ -48,6 +48,7 @@ #include #include #include +#include #include #include #include @@ -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; @@ -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; @@ -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 */ @@ -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 mCommandHandlerObjs; + BitMapObjectPool mTimedHandlers; ReadClient mReadClients[CHIP_IM_MAX_NUM_READ_CLIENT]; ReadHandler mReadHandlers[CHIP_IM_MAX_NUM_READ_HANDLER]; WriteClient mWriteClients[CHIP_IM_MAX_NUM_WRITE_CLIENT]; diff --git a/src/app/MessageDef/InvokeRequestMessage.cpp b/src/app/MessageDef/InvokeRequestMessage.cpp index 2c69d6a41cacaf..7393849cac7491 100644 --- a/src/app/MessageDef/InvokeRequestMessage.cpp +++ b/src/app/MessageDef/InvokeRequestMessage.cpp @@ -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 diff --git a/src/app/TimedHandler.cpp b/src/app/TimedHandler.cpp new file mode 100644 index 00000000000000..d1349f7dd2a4e8 --- /dev/null +++ b/src/app/TimedHandler.cpp @@ -0,0 +1,132 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TimedHandler.h" +#include +#include +#include +#include +#include +#include +#include + +namespace chip { +namespace app { + +CHIP_ERROR TimedHandler::OnMessageReceived(Messaging::ExchangeContext * aExchangeContext, const PayloadHeader & aPayloadHeader, + System::PacketBufferHandle && aPayload) +{ + using namespace Protocols::InteractionModel; + + if (aExchangeContext->IsGroupExchangeContext()) + { + // Timed interactions are always supposed to be unicast. Nothing else + // to do here; exchange will close and we'll free ourselves. + ChipLogError(DataManagement, "Dropping Timed Request on group exchange " ChipLogFormatExchange, + ChipLogValueExchange(aExchangeContext)); + return CHIP_NO_ERROR; + } + + if (mState == State::kExpectingTimedAction) + { + // We were just created; our caller should have done this only if it's + // dealing with a Timed Request message. + VerifyOrDie(aPayloadHeader.HasMessageType(MsgType::TimedRequest)); + mState = State::kReceivedTimedAction; + CHIP_ERROR err = HandleTimedRequestAction(aExchangeContext, aPayloadHeader, std::move(aPayload)); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DataManagement, "Failed to parse Timed Request action: handler %p exchange " ChipLogFormatExchange, this, + ChipLogValueExchange(aExchangeContext)); + StatusResponse::SendStatusResponse(Status::InvalidAction, aExchangeContext, /* aExpectResponse = */ false); + } + return err; + } + + if (mState == State::kExpectingFollowingAction) + { + if (System::SystemClock().GetMonotonicTimestamp() > mTimeLimit) + { + // Time is up. Spec says to send UNSUPPORTED_ACCESS. + ChipLogError(DataManagement, "Timeout expired: handler %p exchange " ChipLogFormatExchange, this, + ChipLogValueExchange(aExchangeContext)); + return StatusResponse::SendStatusResponse(Status::UnsupportedAccess, aExchangeContext, /* aExpectResponse = */ false); + } + + if (aPayloadHeader.HasMessageType(MsgType::InvokeCommandRequest)) + { + auto * imEngine = InteractionModelEngine::GetInstance(); + aExchangeContext->SetDelegate(imEngine); + ChipLogDetail(DataManagement, "Handing timed invoke to IM engine: handler %p exchange " ChipLogFormatExchange, this, + ChipLogValueExchange(aExchangeContext)); + imEngine->OnTimedInvoke(this, aExchangeContext, aPayloadHeader, std::move(aPayload)); + return CHIP_NO_ERROR; + } + + // TODO: Add handling of MsgType::WriteRequest here. + } + + // Not an expected message. Send an error response. The exchange will + // close when we return. + ChipLogError(DataManagement, "Unexpected unknown message in tiemd interaction: handler %p exchange " ChipLogFormatExchange, + this, ChipLogValueExchange(aExchangeContext)); + + return StatusResponse::SendStatusResponse(Status::InvalidAction, aExchangeContext, /* aExpectResponse = */ false); +} + +void TimedHandler::OnExchangeClosing(Messaging::ExchangeContext *) +{ + InteractionModelEngine::GetInstance()->OnTimedInteractionFailed(this); +} + +CHIP_ERROR TimedHandler::HandleTimedRequestAction(Messaging::ExchangeContext * aExchangeContext, + const PayloadHeader & aPayloadHeader, System::PacketBufferHandle && aPayload) +{ + using namespace Protocols::InteractionModel; + + System::PacketBufferTLVReader reader; + reader.Init(std::move(aPayload)); + + reader.Next(); + + TimedRequestMessage::Parser parser; + ReturnErrorOnFailure(parser.Init(reader)); + +#if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK + ReturnErrorOnFailure(parser.CheckSchemaValidity()); +#endif + + uint16_t timeoutMs; + ReturnErrorOnFailure(parser.GetTimeoutMs(&timeoutMs)); + + ChipLogDetail(DataManagement, "Got Timed Request with timeout %" PRIu16 ": handler %p exchange " ChipLogFormatExchange, + timeoutMs, this, ChipLogValueExchange(aExchangeContext)); + // Tell the exchange to close after the timeout passes, so we don't get + // stuck waiting forever if the client never sends another message. + auto delay = System::Clock::Milliseconds32(timeoutMs); + aExchangeContext->SetResponseTimeout(delay); + ReturnErrorOnFailure(StatusResponse::SendStatusResponse(Status::Success, aExchangeContext, /* aExpectResponse = */ true)); + + // Now just wait for the client. + mState = State::kExpectingFollowingAction; + mTimeLimit = System::SystemClock().GetMonotonicTimestamp() + delay; + return CHIP_NO_ERROR; +} + +} // namespace app +} // namespace chip diff --git a/src/app/TimedHandler.h b/src/app/TimedHandler.h new file mode 100644 index 00000000000000..da0f06bd0a0eac --- /dev/null +++ b/src/app/TimedHandler.h @@ -0,0 +1,99 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Definition of a handler for timed interactions. + * + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +/** + * A TimedHandler handles a Timed Request action and then waits for a + * subsequent Invoke or Write action and hands those on to + * InteractionModelEngine if they arrive soon enough. + * + * Lifetime handling: + * + * A TimedHandler is initially allocated when the Timed Request is received and + * becomes the delegate for that exchange. After that it remains alive until + * either the exchange is closed or the interaction is handed on to the + * InteractionModelEngine. + */ + +namespace chip { +namespace app { + +class TimedHandler : public Messaging::ExchangeDelegate +{ +public: + TimedHandler() {} + virtual ~TimedHandler() {} + + // ExchangeDelegate implementation. + CHIP_ERROR OnMessageReceived(Messaging::ExchangeContext * aExchangeContext, const PayloadHeader & aPayloadHeader, + System::PacketBufferHandle && aPayload) override; + +private: + // ExchangeDelegate implementation. + void OnResponseTimeout(Messaging::ExchangeContext *) override + { /* We just want to allow the exchange to close */ + } + void OnExchangeClosing(Messaging::ExchangeContext * aExchangeContext) override; + + void CancelTimer(); + + /** + * Handler for the Timed Request action. This returns success if the Timed + * Request action is parsed successfully and the success Status Response + * action is sent, failure otherwise. + */ + CHIP_ERROR HandleTimedRequestAction(Messaging::ExchangeContext * aExchangeContext, const PayloadHeader & aPayloadHeader, + System::PacketBufferHandle && aPayload); + + enum class State : uint8_t + { + kExpectingTimedAction, // Initial state: expecting a timed action. + kReceivedTimedAction, // Have received the timed action. This can + // be a terminal state if the action ends up + // malformed. + kExpectingFollowingAction, // Expecting write or invoke. + }; + + // Because we have a vtable pointer and mTimeLimit needs to be 8-byte + // aligned on ARM, putting mState first here means we fit in 16 bytes on + // 32-bit ARM, whereas if we put it second we'd be 24 bytes. + // On platforms where either vtable pointers are 8 bytes or 64-bit ints can + // be 4-byte-aligned the ordering here does not matter. + State mState = State::kExpectingTimedAction; + // We keep track of the time limit for message reception, in case our + // exchange's "response expected" timer gets delayed and does not fire when + // the time runs out. + System::Clock::Timestamp mTimeLimit; +}; + +} // namespace app +} // namespace chip diff --git a/src/app/WriteHandler.h b/src/app/WriteHandler.h index 6251a876651921..a60f20b54340fd 100644 --- a/src/app/WriteHandler.h +++ b/src/app/WriteHandler.h @@ -86,6 +86,11 @@ class WriteHandler FabricIndex GetAccessingFabricIndex() const; + /** + * Check whether the WriteRequest we are handling is a timed write. + */ + bool IsTimedWrite() const { return mIsTimedRequest; } + private: enum class State { diff --git a/src/app/tests/AppTestContext.cpp b/src/app/tests/AppTestContext.cpp index 2d3d1b20fbca8c..ee4eee0c07ba40 100644 --- a/src/app/tests/AppTestContext.cpp +++ b/src/app/tests/AppTestContext.cpp @@ -36,6 +36,7 @@ CHIP_ERROR AppContext::Init() CHIP_ERROR AppContext::Shutdown() { + chip::app::InteractionModelEngine::GetInstance()->Shutdown(); ReturnErrorOnFailure(MessagingContext::Shutdown()); ReturnErrorOnFailure(mIOContext.Shutdown()); chip::Platform::MemoryShutdown(); diff --git a/src/app/tests/BUILD.gn b/src/app/tests/BUILD.gn index 703af8c94fc5f4..6ee7a6e30f91ae 100644 --- a/src/app/tests/BUILD.gn +++ b/src/app/tests/BUILD.gn @@ -57,6 +57,7 @@ chip_test_suite("tests") { "TestReadInteraction.cpp", "TestReportingEngine.cpp", "TestStatusResponseMessage.cpp", + "TestTimedHandler.cpp", "TestWriteInteraction.cpp", ] diff --git a/src/app/tests/TestCommandInteraction.cpp b/src/app/tests/TestCommandInteraction.cpp index c67cfb5db9fd18..3842f1b429a200 100644 --- a/src/app/tests/TestCommandInteraction.cpp +++ b/src/app/tests/TestCommandInteraction.cpp @@ -190,7 +190,7 @@ class TestCommandInteraction private: static void GenerateInvokeRequest(nlTestSuite * apSuite, void * apContext, System::PacketBufferHandle & aPayload, - bool aNeedCommandData, EndpointId aEndpointId = kTestEndpointId, + bool aNeedCommandData, bool aIsTimedRequest, EndpointId aEndpointId = kTestEndpointId, ClusterId aClusterId = kTestClusterId, CommandId aCommandId = kTestCommandId); static void GenerateInvokeResponse(nlTestSuite * apSuite, void * apContext, System::PacketBufferHandle & aPayload, bool aNeedCommandData, EndpointId aEndpointId = kTestEndpointId, @@ -219,8 +219,8 @@ CommandPathParams MakeTestCommandPath(CommandId aCommandId = kTestCommandId) } void TestCommandInteraction::GenerateInvokeRequest(nlTestSuite * apSuite, void * apContext, System::PacketBufferHandle & aPayload, - bool aNeedCommandData, EndpointId aEndpointId, ClusterId aClusterId, - CommandId aCommandId) + bool aNeedCommandData, bool aIsTimedRequest, EndpointId aEndpointId, + ClusterId aClusterId, CommandId aCommandId) { CHIP_ERROR err = CHIP_NO_ERROR; @@ -231,7 +231,7 @@ void TestCommandInteraction::GenerateInvokeRequest(nlTestSuite * apSuite, void * err = invokeRequestMessageBuilder.Init(&writer); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); - invokeRequestMessageBuilder.SuppressResponse(true).TimedRequest(true); + invokeRequestMessageBuilder.SuppressResponse(true).TimedRequest(aIsTimedRequest); InvokeRequests::Builder invokeRequests = invokeRequestMessageBuilder.CreateInvokeRequests(); NL_TEST_ASSERT(apSuite, invokeRequestMessageBuilder.GetError() == CHIP_NO_ERROR); @@ -548,8 +548,8 @@ void TestCommandInteraction::TestCommandHandlerWithProcessReceivedMsg(nlTestSuit app::CommandHandler commandHandler(&mockCommandHandlerDelegate); System::PacketBufferHandle commandDatabuf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize); - GenerateInvokeRequest(apSuite, apContext, commandDatabuf, true /*aNeedCommandData*/); - err = commandHandler.ProcessInvokeRequest(std::move(commandDatabuf)); + GenerateInvokeRequest(apSuite, apContext, commandDatabuf, true /*aNeedCommandData*/, /* aIsTimedRequest = */ false); + err = commandHandler.ProcessInvokeRequest(std::move(commandDatabuf), false); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); } @@ -560,28 +560,42 @@ void TestCommandInteraction::TestCommandHandlerWithProcessReceivedNotExistComman System::PacketBufferHandle commandDatabuf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize); // Use some invalid endpoint / cluster / command. - GenerateInvokeRequest(apSuite, apContext, commandDatabuf, false /*aNeedCommandData*/, 0xDE /* endpoint */, 0xADBE /* cluster */, - 0xEF /* command */); + GenerateInvokeRequest(apSuite, apContext, commandDatabuf, false /*aNeedCommandData*/, /* aIsTimedRequest = */ false, + 0xDE /* endpoint */, 0xADBE /* cluster */, 0xEF /* command */); // TODO: Need to find a way to get the response instead of only check if a function on key path is called. // We should not reach CommandDispatch if requested command does not exist. chip::isCommandDispatched = false; - err = commandHandler.ProcessInvokeRequest(std::move(commandDatabuf)); + err = commandHandler.ProcessInvokeRequest(std::move(commandDatabuf), false); NL_TEST_ASSERT(apSuite, !chip::isCommandDispatched); } void TestCommandInteraction::TestCommandHandlerWithProcessReceivedEmptyDataMsg(nlTestSuite * apSuite, void * apContext) { - CHIP_ERROR err = CHIP_NO_ERROR; - app::CommandHandler commandHandler(&mockCommandHandlerDelegate); - System::PacketBufferHandle commandDatabuf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize); - - chip::isCommandDispatched = false; - - GenerateInvokeRequest(apSuite, apContext, commandDatabuf, false /*aNeedCommandData*/); - - err = commandHandler.ProcessInvokeRequest(std::move(commandDatabuf)); - NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR && chip::isCommandDispatched); + bool allBooleans[] = { true, false }; + for (auto messageIsTimed : allBooleans) + { + for (auto transactionIsTimed : allBooleans) + { + CHIP_ERROR err = CHIP_NO_ERROR; + app::CommandHandler commandHandler(&mockCommandHandlerDelegate); + System::PacketBufferHandle commandDatabuf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize); + + chip::isCommandDispatched = false; + + GenerateInvokeRequest(apSuite, apContext, commandDatabuf, false /*aNeedCommandData*/, messageIsTimed); + + err = commandHandler.ProcessInvokeRequest(std::move(commandDatabuf), transactionIsTimed); + if (messageIsTimed == transactionIsTimed) + { + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR && chip::isCommandDispatched); + } + else + { + NL_TEST_ASSERT(apSuite, err != CHIP_NO_ERROR && !chip::isCommandDispatched); + } + } + } } void TestCommandInteraction::TestCommandSenderCommandSuccessResponseFlow(nlTestSuite * apSuite, void * apContext) diff --git a/src/app/tests/TestTimedHandler.cpp b/src/app/tests/TestTimedHandler.cpp new file mode 100644 index 00000000000000..9855a036991c60 --- /dev/null +++ b/src/app/tests/TestTimedHandler.cpp @@ -0,0 +1,240 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using TestContext = chip::Test::AppContext; + +namespace chip { +namespace app { + +using namespace Messaging; +using namespace Protocols::InteractionModel; + +class TestTimedHandler +{ +public: + static void TestInvokeFastEnough(nlTestSuite * aSuite, void * aContext); + static void TestInvokeTooSlow(nlTestSuite * aSuite, void * aContext); + + static void TestInvokeNeverComes(nlTestSuite * aSuite, void * aContext); + +private: + static void GenerateTimedRequest(nlTestSuite * aSuite, uint16_t aTimeoutValue, System::PacketBufferHandle & aPayload); +}; + +namespace { + +class TestExchangeDelegate : public Messaging::ExchangeDelegate +{ + CHIP_ERROR OnMessageReceived(Messaging::ExchangeContext * aExchangeContext, const PayloadHeader & aPayloadHeader, + System::PacketBufferHandle && aPayload) override + { + mNewMessageReceived = true; + mLastMessageWasStatus = aPayloadHeader.HasMessageType(MsgType::StatusResponse); + if (mLastMessageWasStatus) + { + mStatus.mStatus = Status::Failure; + StatusResponse::ProcessStatusResponse(std::move(aPayload), mStatus); + } + if (mKeepExchangeOpen) + { + aExchangeContext->WillSendMessage(); + } + return CHIP_NO_ERROR; + } + + void OnResponseTimeout(Messaging::ExchangeContext *) override {} + +public: + bool mKeepExchangeOpen = false; + bool mNewMessageReceived = false; + bool mLastMessageWasStatus = false; + StatusIB mStatus; +}; + +} // anonymous namespace + +void TestTimedHandler::GenerateTimedRequest(nlTestSuite * aSuite, uint16_t aTimeoutValue, System::PacketBufferHandle & aPayload) +{ + aPayload = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize); + NL_TEST_ASSERT(aSuite, !aPayload.IsNull()); + + System::PacketBufferTLVWriter writer; + writer.Init(std::move(aPayload)); + + TimedRequestMessage::Builder builder; + CHIP_ERROR err = builder.Init(&writer); + NL_TEST_ASSERT(aSuite, err == CHIP_NO_ERROR); + + builder.TimeoutMs(aTimeoutValue); + NL_TEST_ASSERT(aSuite, builder.GetError() == CHIP_NO_ERROR); + + err = writer.Finalize(&aPayload); + NL_TEST_ASSERT(aSuite, err == CHIP_NO_ERROR); +} + +void TestTimedHandler::TestInvokeFastEnough(nlTestSuite * aSuite, void * aContext) +{ + TestContext & ctx = *static_cast(aContext); + + System::PacketBufferHandle payload; + GenerateTimedRequest(aSuite, 50, payload); + + TestExchangeDelegate delegate; + ExchangeContext * exchange = ctx.NewExchangeToAlice(&delegate); + NL_TEST_ASSERT(aSuite, exchange != nullptr); + + NL_TEST_ASSERT(aSuite, !delegate.mNewMessageReceived); + + delegate.mKeepExchangeOpen = true; + + CHIP_ERROR err = exchange->SendMessage(MsgType::TimedRequest, std::move(payload), SendMessageFlags::kExpectResponse); + NL_TEST_ASSERT(aSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(aSuite, delegate.mNewMessageReceived); + NL_TEST_ASSERT(aSuite, delegate.mLastMessageWasStatus); + NL_TEST_ASSERT(aSuite, delegate.mStatus.mStatus == Status::Success); + + // Send an empty payload, which will error out but not with the + // UNSUPPORTED_ACCESS status we expect if we miss our timeout. + payload = MessagePacketBuffer::New(0); + NL_TEST_ASSERT(aSuite, !payload.IsNull()); + + delegate.mKeepExchangeOpen = false; + delegate.mNewMessageReceived = false; + + err = exchange->SendMessage(MsgType::InvokeCommandRequest, std::move(payload)); + NL_TEST_ASSERT(aSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(aSuite, delegate.mNewMessageReceived); + NL_TEST_ASSERT(aSuite, delegate.mLastMessageWasStatus); + NL_TEST_ASSERT(aSuite, delegate.mStatus.mStatus != Status::UnsupportedAccess); +} + +void TestTimedHandler::TestInvokeTooSlow(nlTestSuite * aSuite, void * aContext) +{ + TestContext & ctx = *static_cast(aContext); + + System::PacketBufferHandle payload; + GenerateTimedRequest(aSuite, 50, payload); + + TestExchangeDelegate delegate; + ExchangeContext * exchange = ctx.NewExchangeToAlice(&delegate); + NL_TEST_ASSERT(aSuite, exchange != nullptr); + + NL_TEST_ASSERT(aSuite, !delegate.mNewMessageReceived); + + delegate.mKeepExchangeOpen = true; + + CHIP_ERROR err = exchange->SendMessage(MsgType::TimedRequest, std::move(payload), SendMessageFlags::kExpectResponse); + NL_TEST_ASSERT(aSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(aSuite, delegate.mNewMessageReceived); + NL_TEST_ASSERT(aSuite, delegate.mLastMessageWasStatus); + NL_TEST_ASSERT(aSuite, delegate.mStatus.mStatus == Status::Success); + + // Sleep for > 50ms so we miss our time window. + chip::test_utils::SleepMillis(75); + + // Send an empty payload, which will error out but not with the + // UNSUPPORTED_ACCESS status we expect if we miss our timeout. + payload = MessagePacketBuffer::New(0); + NL_TEST_ASSERT(aSuite, !payload.IsNull()); + + delegate.mKeepExchangeOpen = false; + delegate.mNewMessageReceived = false; + + err = exchange->SendMessage(MsgType::InvokeCommandRequest, std::move(payload)); + NL_TEST_ASSERT(aSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(aSuite, delegate.mNewMessageReceived); + NL_TEST_ASSERT(aSuite, delegate.mLastMessageWasStatus); + NL_TEST_ASSERT(aSuite, delegate.mStatus.mStatus == Status::UnsupportedAccess); +} + +void TestTimedHandler::TestInvokeNeverComes(nlTestSuite * aSuite, void * aContext) +{ + TestContext & ctx = *static_cast(aContext); + + System::PacketBufferHandle payload; + GenerateTimedRequest(aSuite, 50, payload); + + TestExchangeDelegate delegate; + ExchangeContext * exchange = ctx.NewExchangeToAlice(&delegate); + NL_TEST_ASSERT(aSuite, exchange != nullptr); + + NL_TEST_ASSERT(aSuite, !delegate.mNewMessageReceived); + + CHIP_ERROR err = exchange->SendMessage(MsgType::TimedRequest, std::move(payload), SendMessageFlags::kExpectResponse); + NL_TEST_ASSERT(aSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(aSuite, delegate.mNewMessageReceived); + NL_TEST_ASSERT(aSuite, delegate.mLastMessageWasStatus); + NL_TEST_ASSERT(aSuite, delegate.mStatus.mStatus == Status::Success); + + // Do nothing else; exchange on the server remains open. We are testing to + // see whether shutdown cleans it up properly. +} + +} // namespace app +} // namespace chip + +namespace { + +/** + * Test Suite. It lists all the test functions. + */ + +// clang-format off +const nlTest sTests[] = +{ + NL_TEST_DEF("TimedHandlerTestInvokeFastEnough", chip::app::TestTimedHandler::TestInvokeFastEnough), + NL_TEST_DEF("TimedHandlerTestInvokeTooSlow", chip::app::TestTimedHandler::TestInvokeTooSlow), + NL_TEST_DEF("TimedHandlerTestInvokeNeverComes", chip::app::TestTimedHandler::TestInvokeNeverComes), + NL_TEST_SENTINEL() +}; +// clang-format on + +// clang-format off +nlTestSuite sSuite = +{ + "TestTimedHandler", + &sTests[0], + TestContext::Initialize, + TestContext::Finalize +}; +// clang-format on + +} // namespace + +int TestTimedHandler() +{ + TestContext gContext; + nlTestRunner(&sSuite, &gContext); + return (nlTestRunnerStats(&sSuite)); +} + +CHIP_REGISTER_TEST_SUITE(TestTimedHandler) diff --git a/src/app/util/ember-compatibility-functions.cpp b/src/app/util/ember-compatibility-functions.cpp index 90298769c21cbc..8f3c54e6763d59 100644 --- a/src/app/util/ember-compatibility-functions.cpp +++ b/src/app/util/ember-compatibility-functions.cpp @@ -702,6 +702,11 @@ CHIP_ERROR WriteSingleClusterData(ClusterInfo & aClusterInfo, TLV::TLVReader & a return apWriteHandler->AddStatus(attributePathParams, Protocols::InteractionModel::Status::UnsupportedWrite); } + if (attributeMetadata->MustUseTimedWrite() && !apWriteHandler->IsTimedWrite()) + { + return apWriteHandler->AddStatus(attributePathParams, Protocols::InteractionModel::Status::NeedsTimedInteraction); + } + AttributeAccessInterface * attrOverride = findAttributeAccessOverride(aClusterInfo.mEndpointId, aClusterInfo.mClusterId); if (attrOverride != nullptr) { diff --git a/src/app/zap-templates/partials/im_command_handler_cluster_commands.zapt b/src/app/zap-templates/partials/im_command_handler_cluster_commands.zapt index 3f7e5ec2f1375a..f989635a19c965 100644 --- a/src/app/zap-templates/partials/im_command_handler_cluster_commands.zapt +++ b/src/app/zap-templates/partials/im_command_handler_cluster_commands.zapt @@ -1,4 +1,10 @@ {{#if (isServer parent.side)}} +{{#if mustUseTimedInvoke}} +if (!apCommandObj->IsTimedInvoke()) { + apCommandObj->AddStatus(aCommandPath, Protocols::InteractionModel::Status::NeedsTimedInteraction); + return; +} +{{/if}} Commands::{{asUpperCamelCase name}}::DecodableType commandData; TLVError = DataModel::Decode(aDataTlv, commandData); if (TLVError == CHIP_NO_ERROR) { diff --git a/src/lib/core/CHIPConfig.h b/src/lib/core/CHIPConfig.h index 3d13987061d868..4489d2fe189e87 100644 --- a/src/lib/core/CHIPConfig.h +++ b/src/lib/core/CHIPConfig.h @@ -2427,6 +2427,7 @@ extern const char CHIP_NON_PRODUCTION_MARKER[]; * * #CHIP_IM_SERVER_MAX_NUM_PATH_GROUPS * * #CHIP_IM_MAX_NUM_WRITE_HANDLER * * #CHIP_IM_MAX_NUM_WRITE_CLIENT + * * #CHIP_IM_MAX_NUM_TIMED_HANDLER * * @{ */ @@ -2494,6 +2495,16 @@ extern const char CHIP_NON_PRODUCTION_MARKER[]; #define CHIP_IM_MAX_NUM_WRITE_CLIENT 4 #endif +/** + * @def CHIP_IM_MAX_NUM_TIMED_HANDLER + * + * @brief Defines the maximum number of TimedHandler, limits the number of + * active timed interactions waiting for the Invoke or Write. + */ +#ifndef CHIP_IM_MAX_NUM_TIMED_HANDLER +#define CHIP_IM_MAX_NUM_TIMED_HANDLER 8 +#endif + /** * @def CONFIG_IM_BUILD_FOR_UNIT_TEST * diff --git a/src/messaging/ExchangeMgr.cpp b/src/messaging/ExchangeMgr.cpp index 0826c118b71b5c..7ccb0ce7b0db8e 100644 --- a/src/messaging/ExchangeMgr.cpp +++ b/src/messaging/ExchangeMgr.cpp @@ -223,6 +223,9 @@ void ExchangeManager::OnMessageReceived(const PacketHeader & packetHeader, const ec->SetMsgRcvdFromPeer(true); } + ChipLogDetail(ExchangeManager, "Found matching exchange: " ChipLogFormatExchange ", Delegate: 0x%p", + ChipLogValueExchange(ec), ec->GetDelegate()); + // Matched ExchangeContext; send to message handler. ec->HandleMessage(packetHeader.GetMessageCounter(), payloadHeader, source, msgFlags, std::move(msgBuf)); found = true; diff --git a/zzz_generated/all-clusters-app/zap-generated/IMClusterCommandHandler.cpp b/zzz_generated/all-clusters-app/zap-generated/IMClusterCommandHandler.cpp index eae6818c1b568f..9912a355878ef4 100644 --- a/zzz_generated/all-clusters-app/zap-generated/IMClusterCommandHandler.cpp +++ b/zzz_generated/all-clusters-app/zap-generated/IMClusterCommandHandler.cpp @@ -1758,6 +1758,11 @@ void DispatchServerCommand(CommandHandler * apCommandObj, const ConcreteCommandP break; } case Commands::TimedInvokeRequest::Id: { + if (!apCommandObj->IsTimedInvoke()) + { + apCommandObj->AddStatus(aCommandPath, Protocols::InteractionModel::Status::NeedsTimedInteraction); + return; + } Commands::TimedInvokeRequest::DecodableType commandData; TLVError = DataModel::Decode(aDataTlv, commandData); if (TLVError == CHIP_NO_ERROR)