Implement server support for timed write. (#12441)
diff --git a/src/app/InteractionModelEngine.cpp b/src/app/InteractionModelEngine.cpp
index a3f2c9f..6ebd370 100644
--- a/src/app/InteractionModelEngine.cpp
+++ b/src/app/InteractionModelEngine.cpp
@@ -349,9 +349,10 @@
return CHIP_NO_ERROR;
}
-CHIP_ERROR InteractionModelEngine::OnWriteRequest(Messaging::ExchangeContext * apExchangeContext,
- const PayloadHeader & aPayloadHeader, System::PacketBufferHandle && aPayload,
- Protocols::InteractionModel::Status & aStatus)
+Protocols::InteractionModel::Status InteractionModelEngine::OnWriteRequest(Messaging::ExchangeContext * apExchangeContext,
+ const PayloadHeader & aPayloadHeader,
+ System::PacketBufferHandle && aPayload,
+ bool aIsTimedWrite)
{
ChipLogDetail(InteractionModel, "Received Write request");
@@ -359,15 +360,12 @@
{
if (writeHandler.IsFree())
{
- ReturnErrorOnFailure(writeHandler.Init(mpDelegate));
- ReturnErrorOnFailure(writeHandler.OnWriteRequest(apExchangeContext, std::move(aPayload)));
- aStatus = Protocols::InteractionModel::Status::Success;
- return CHIP_NO_ERROR;
+ VerifyOrReturnError(writeHandler.Init(mpDelegate) == CHIP_NO_ERROR, Status::Busy);
+ return writeHandler.OnWriteRequest(apExchangeContext, std::move(aPayload), aIsTimedWrite);
}
}
ChipLogProgress(InteractionModel, "no resource for write interaction");
- aStatus = Protocols::InteractionModel::Status::Busy;
- return CHIP_NO_ERROR;
+ return Status::Busy;
}
CHIP_ERROR InteractionModelEngine::OnTimedRequest(Messaging::ExchangeContext * apExchangeContext,
@@ -438,7 +436,7 @@
}
else if (aPayloadHeader.HasMessageType(Protocols::InteractionModel::MsgType::WriteRequest))
{
- SuccessOrExit(OnWriteRequest(apExchangeContext, aPayloadHeader, std::move(aPayload), status));
+ status = OnWriteRequest(apExchangeContext, aPayloadHeader, std::move(aPayload), /* aIsTimedWrite = */ false);
}
else if (aPayloadHeader.HasMessageType(Protocols::InteractionModel::MsgType::SubscribeRequest))
{
@@ -722,5 +720,25 @@
}
}
+void InteractionModelEngine::OnTimedWrite(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 write.
+ apExchangeContext->SetDelegate(this);
+ mTimedHandlers.Deallocate(apTimedHandler);
+
+ VerifyOrDie(aPayloadHeader.HasMessageType(MsgType::WriteRequest));
+ VerifyOrDie(!apExchangeContext->IsGroupExchangeContext());
+
+ Status status = OnWriteRequest(apExchangeContext, aPayloadHeader, std::move(aPayload), /* aIsTimedWrite = */ true);
+ if (status != Status::Success)
+ {
+ StatusResponse::Send(status, apExchangeContext, /* aExpectResponse = */ false);
+ }
+}
+
} // namespace app
} // namespace chip
diff --git a/src/app/InteractionModelEngine.h b/src/app/InteractionModelEngine.h
index beeca95..c235c7d 100644
--- a/src/app/InteractionModelEngine.h
+++ b/src/app/InteractionModelEngine.h
@@ -209,9 +209,17 @@
void OnTimedInvoke(TimedHandler * apTimedHandler, Messaging::ExchangeContext * apExchangeContext,
const PayloadHeader & aPayloadHeader, System::PacketBufferHandle && aPayload);
+ /**
+ * Called when a timed write is received. This function takes over all
+ * handling of the exchange, status reporting, and so forth.
+ */
+ void OnTimedWrite(TimedHandler * apTimedHandler, Messaging::ExchangeContext * apExchangeContext,
+ const PayloadHeader & aPayloadHeader, System::PacketBufferHandle && aPayload);
+
private:
friend class reporting::Engine;
friend class TestCommandInteraction;
+ using Status = Protocols::InteractionModel::Status;
void OnDone(CommandHandler & apCommandObj) override;
@@ -239,11 +247,12 @@
/**
* Called when Interaction Model receives a Write Request message. Errors processing
- * the Write 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.
+ * the Write Request are handled entirely within this function. If the
+ * status returned is not Status::Success, the caller will send a status
+ * response message with that status.
*/
- CHIP_ERROR OnWriteRequest(Messaging::ExchangeContext * apExchangeContext, const PayloadHeader & aPayloadHeader,
- System::PacketBufferHandle && aPayload, Protocols::InteractionModel::Status & aStatus);
+ Status OnWriteRequest(Messaging::ExchangeContext * apExchangeContext, const PayloadHeader & aPayloadHeader,
+ System::PacketBufferHandle && aPayload, bool aIsTimedWrite);
/**
* Called when Interaction Model receives a Timed Request message. Errors processing
diff --git a/src/app/TimedHandler.cpp b/src/app/TimedHandler.cpp
index c29ec04..ae71dcb 100644
--- a/src/app/TimedHandler.cpp
+++ b/src/app/TimedHandler.cpp
@@ -71,14 +71,20 @@
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.
+ if (aPayloadHeader.HasMessageType(MsgType::WriteRequest))
+ {
+ auto * imEngine = InteractionModelEngine::GetInstance();
+ ChipLogDetail(DataManagement, "Handing timed write to IM engine: handler %p exchange " ChipLogFormatExchange, this,
+ ChipLogValueExchange(aExchangeContext));
+ imEngine->OnTimedWrite(this, aExchangeContext, aPayloadHeader, std::move(aPayload));
+ return CHIP_NO_ERROR;
+ }
}
// Not an expected message. Send an error response. The exchange will
diff --git a/src/app/WriteHandler.cpp b/src/app/WriteHandler.cpp
index 2cd1bcd..ff3ffcf 100644
--- a/src/app/WriteHandler.cpp
+++ b/src/app/WriteHandler.cpp
@@ -26,6 +26,9 @@
namespace chip {
namespace app {
+
+using namespace Protocols::InteractionModel;
+
CHIP_ERROR WriteHandler::Init(InteractionModelDelegate * apDelegate)
{
IgnoreUnusedVariable(apDelegate);
@@ -53,23 +56,25 @@
ClearState();
}
-CHIP_ERROR WriteHandler::OnWriteRequest(Messaging::ExchangeContext * apExchangeContext, System::PacketBufferHandle && aPayload)
+Status WriteHandler::OnWriteRequest(Messaging::ExchangeContext * apExchangeContext, System::PacketBufferHandle && aPayload,
+ bool aIsTimedWrite)
{
- CHIP_ERROR err = CHIP_NO_ERROR;
- mpExchangeCtx = apExchangeContext;
+ mpExchangeCtx = apExchangeContext;
- err = ProcessWriteRequest(std::move(aPayload));
- SuccessOrExit(err);
+ Status status = ProcessWriteRequest(std::move(aPayload), aIsTimedWrite);
// Do not send response on Group Write
- if (!apExchangeContext->IsGroupExchangeContext())
+ if (status == Status::Success && !apExchangeContext->IsGroupExchangeContext())
{
- err = SendWriteResponse();
+ CHIP_ERROR err = SendWriteResponse();
+ if (err != CHIP_NO_ERROR)
+ {
+ status = Status::Failure;
+ }
}
-exit:
Shutdown();
- return err;
+ return status;
}
CHIP_ERROR WriteHandler::FinalizeMessage(System::PacketBufferHandle & packet)
@@ -190,7 +195,7 @@
return err;
}
-CHIP_ERROR WriteHandler::ProcessWriteRequest(System::PacketBufferHandle && aPayload)
+Status WriteHandler::ProcessWriteRequest(System::PacketBufferHandle && aPayload, bool aIsTimedWrite)
{
CHIP_ERROR err = CHIP_NO_ERROR;
System::PacketBufferTLVReader reader;
@@ -199,6 +204,14 @@
AttributeDataIBs::Parser AttributeDataIBsParser;
TLV::TLVReader AttributeDataIBsReader;
bool needSuppressResponse = false;
+ // Default to InvalidAction for our status; that's what we want if any of
+ // the parsing of our overall structure or paths fails. Once we have a
+ // successfully parsed path, the only way we will get a failure return is if
+ // our path handling fails to AddStatus on us.
+ //
+ // TODO: That's not technically InvalidAction, and we should probably make
+ // our callees hand out Status as well.
+ Status status = Status::InvalidAction;
reader.Init(std::move(aPayload));
@@ -228,11 +241,23 @@
err = writeRequestParser.GetWriteRequests(&AttributeDataIBsParser);
SuccessOrExit(err);
+ if (mIsTimedRequest != aIsTimedWrite)
+ {
+ // 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.
+ status = Status::UnsupportedAccess;
+ goto exit;
+ }
+
AttributeDataIBsParser.GetReader(&AttributeDataIBsReader);
err = ProcessAttributeDataIBs(AttributeDataIBsReader);
+ if (err == CHIP_NO_ERROR)
+ {
+ status = Status::Success;
+ }
exit:
- return err;
+ return status;
}
CHIP_ERROR WriteHandler::AddStatus(const AttributePathParams & aAttributePathParams,
diff --git a/src/app/WriteHandler.h b/src/app/WriteHandler.h
index a60f20b..b6c8386 100644
--- a/src/app/WriteHandler.h
+++ b/src/app/WriteHandler.h
@@ -29,6 +29,7 @@
#include <messaging/ExchangeMgr.h>
#include <messaging/Flags.h>
#include <protocols/Protocols.h>
+#include <protocols/interaction_model/Constants.h>
#include <system/SystemPacketBuffer.h>
namespace chip {
@@ -60,11 +61,13 @@
*
* @param[in] apExchangeContext A pointer to the ExchangeContext.
* @param[in] aPayload A payload that has read request data
+ * @param[in] aIsTimedWrite Whether write is part of a timed interaction.
*
- * @retval #Others If fails to process read request
- * @retval #CHIP_NO_ERROR On success.
+ * @retval Status. Callers are expected to send a status response if the
+ * return status is not Status::Success.
*/
- CHIP_ERROR OnWriteRequest(Messaging::ExchangeContext * apExchangeContext, System::PacketBufferHandle && aPayload);
+ Protocols::InteractionModel::Status OnWriteRequest(Messaging::ExchangeContext * apExchangeContext,
+ System::PacketBufferHandle && aPayload, bool aIsTimedWrite);
bool IsFree() const { return mState == State::Uninitialized; }
@@ -99,7 +102,7 @@
AddStatus, // The handler has added status code
Sending, // The handler has sent out the write response
};
- CHIP_ERROR ProcessWriteRequest(System::PacketBufferHandle && aPayload);
+ Protocols::InteractionModel::Status ProcessWriteRequest(System::PacketBufferHandle && aPayload, bool aIsTimedWrite);
CHIP_ERROR FinalizeMessage(System::PacketBufferHandle & packet);
CHIP_ERROR SendWriteResponse();
diff --git a/src/app/tests/TestTimedHandler.cpp b/src/app/tests/TestTimedHandler.cpp
index 9855a03..cf6dfd9 100644
--- a/src/app/tests/TestTimedHandler.cpp
+++ b/src/app/tests/TestTimedHandler.cpp
@@ -42,11 +42,17 @@
{
public:
static void TestInvokeFastEnough(nlTestSuite * aSuite, void * aContext);
+ static void TestWriteFastEnough(nlTestSuite * aSuite, void * aContext);
+
static void TestInvokeTooSlow(nlTestSuite * aSuite, void * aContext);
+ static void TestWriteTooSlow(nlTestSuite * aSuite, void * aContext);
static void TestInvokeNeverComes(nlTestSuite * aSuite, void * aContext);
private:
+ static void TestFollowingMessageFastEnough(nlTestSuite * aSuite, void * aContext, MsgType aMsgType);
+ static void TestFollowingMessageTooSlow(nlTestSuite * aSuite, void * aContext, MsgType aMsgType);
+
static void GenerateTimedRequest(nlTestSuite * aSuite, uint16_t aTimeoutValue, System::PacketBufferHandle & aPayload);
};
@@ -101,7 +107,7 @@
NL_TEST_ASSERT(aSuite, err == CHIP_NO_ERROR);
}
-void TestTimedHandler::TestInvokeFastEnough(nlTestSuite * aSuite, void * aContext)
+void TestTimedHandler::TestFollowingMessageFastEnough(nlTestSuite * aSuite, void * aContext, MsgType aMsgType)
{
TestContext & ctx = *static_cast<TestContext *>(aContext);
@@ -130,14 +136,24 @@
delegate.mKeepExchangeOpen = false;
delegate.mNewMessageReceived = false;
- err = exchange->SendMessage(MsgType::InvokeCommandRequest, std::move(payload));
+ err = exchange->SendMessage(aMsgType, 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)
+void TestTimedHandler::TestInvokeFastEnough(nlTestSuite * aSuite, void * aContext)
+{
+ TestFollowingMessageFastEnough(aSuite, aContext, MsgType::InvokeCommandRequest);
+}
+
+void TestTimedHandler::TestWriteFastEnough(nlTestSuite * aSuite, void * aContext)
+{
+ TestFollowingMessageFastEnough(aSuite, aContext, MsgType::WriteRequest);
+}
+
+void TestTimedHandler::TestFollowingMessageTooSlow(nlTestSuite * aSuite, void * aContext, MsgType aMsgType)
{
TestContext & ctx = *static_cast<TestContext *>(aContext);
@@ -169,13 +185,23 @@
delegate.mKeepExchangeOpen = false;
delegate.mNewMessageReceived = false;
- err = exchange->SendMessage(MsgType::InvokeCommandRequest, std::move(payload));
+ err = exchange->SendMessage(aMsgType, 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)
+{
+ TestFollowingMessageTooSlow(aSuite, aContext, MsgType::InvokeCommandRequest);
+}
+
+void TestTimedHandler::TestWriteTooSlow(nlTestSuite * aSuite, void * aContext)
+{
+ TestFollowingMessageTooSlow(aSuite, aContext, MsgType::WriteRequest);
+}
+
void TestTimedHandler::TestInvokeNeverComes(nlTestSuite * aSuite, void * aContext)
{
TestContext & ctx = *static_cast<TestContext *>(aContext);
diff --git a/src/app/tests/TestWriteInteraction.cpp b/src/app/tests/TestWriteInteraction.cpp
index 24c6ee8..5ac7c28 100644
--- a/src/app/tests/TestWriteInteraction.cpp
+++ b/src/app/tests/TestWriteInteraction.cpp
@@ -52,7 +52,8 @@
private:
static void AddAttributeDataIB(nlTestSuite * apSuite, void * apContext, WriteClientHandle & aWriteClient);
static void AddAttributeStatus(nlTestSuite * apSuite, void * apContext, WriteHandler & aWriteHandler);
- static void GenerateWriteRequest(nlTestSuite * apSuite, void * apContext, System::PacketBufferHandle & aPayload);
+ static void GenerateWriteRequest(nlTestSuite * apSuite, void * apContext, bool aIsTimedWrite,
+ System::PacketBufferHandle & aPayload);
static void GenerateWriteResponse(nlTestSuite * apSuite, void * apContext, System::PacketBufferHandle & aPayload);
};
@@ -115,7 +116,8 @@
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
}
-void TestWriteInteraction::GenerateWriteRequest(nlTestSuite * apSuite, void * apContext, System::PacketBufferHandle & aPayload)
+void TestWriteInteraction::GenerateWriteRequest(nlTestSuite * apSuite, void * apContext, bool aIsTimedWrite,
+ System::PacketBufferHandle & aPayload)
{
CHIP_ERROR err = CHIP_NO_ERROR;
System::PacketBufferTLVWriter writer;
@@ -155,7 +157,7 @@
attributeDataIBsBuilder.EndOfAttributeDataIBs();
NL_TEST_ASSERT(apSuite, attributeDataIBsBuilder.GetError() == CHIP_NO_ERROR);
- writeRequestBuilder.TimedRequest(false).IsFabricFiltered(false).EndOfWriteRequestMessage();
+ writeRequestBuilder.TimedRequest(aIsTimedWrite).IsFabricFiltered(false).EndOfWriteRequestMessage();
NL_TEST_ASSERT(apSuite, writeRequestBuilder.GetError() == CHIP_NO_ERROR);
err = writer.Finalize(&aPayload);
@@ -263,25 +265,48 @@
void TestWriteInteraction::TestWriteHandler(nlTestSuite * apSuite, void * apContext)
{
+ using namespace Protocols::InteractionModel;
+
TestContext & ctx = *static_cast<TestContext *>(apContext);
- CHIP_ERROR err = CHIP_NO_ERROR;
+ constexpr bool allBooleans[] = { true, false };
+ for (auto messageIsTimed : allBooleans)
+ {
+ for (auto transactionIsTimed : allBooleans)
+ {
+ CHIP_ERROR err = CHIP_NO_ERROR;
- app::WriteHandler writeHandler;
+ app::WriteHandler writeHandler;
- chip::app::InteractionModelDelegate IMdelegate;
- System::PacketBufferHandle buf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize);
- err = writeHandler.Init(&IMdelegate);
+ chip::app::InteractionModelDelegate IMdelegate;
+ System::PacketBufferHandle buf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize);
+ err = writeHandler.Init(&IMdelegate);
- GenerateWriteRequest(apSuite, apContext, buf);
+ GenerateWriteRequest(apSuite, apContext, messageIsTimed, buf);
- TestExchangeDelegate delegate;
- Messaging::ExchangeContext * exchange = ctx.NewExchangeToBob(&delegate);
- err = writeHandler.OnWriteRequest(exchange, std::move(buf));
- NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
+ TestExchangeDelegate delegate;
+ Messaging::ExchangeContext * exchange = ctx.NewExchangeToBob(&delegate);
+ Status status = writeHandler.OnWriteRequest(exchange, std::move(buf), transactionIsTimed);
+ if (messageIsTimed == transactionIsTimed)
+ {
+ NL_TEST_ASSERT(apSuite, status == Status::Success);
+ }
+ else
+ {
+ NL_TEST_ASSERT(apSuite, status == Status::UnsupportedAccess);
+ // In the normal code flow, the exchange would now get closed
+ // when we send the error status on it (of if that fails when
+ // the stack unwinds). In the success case it's been closed
+ // already by the WriteHandler sending the response on it, but
+ // if we are in the error case we need to make sure it gets
+ // closed.
+ exchange->Close();
+ }
- Messaging::ReliableMessageMgr * rm = ctx.GetExchangeManager().GetReliableMessageMgr();
- NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0);
+ Messaging::ReliableMessageMgr * rm = ctx.GetExchangeManager().GetReliableMessageMgr();
+ NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0);
+ }
+ }
}
CHIP_ERROR WriteSingleClusterData(ClusterInfo & aClusterInfo, TLV::TLVReader & aReader, WriteHandler * aWriteHandler)