From d7a6c2e03aec33ea6e3f8ad779898fcfdbe91231 Mon Sep 17 00:00:00 2001 From: yunhanw-google Date: Fri, 18 Jun 2021 13:01:52 -0700 Subject: [PATCH] Add initial IM write client/handler (#7730) --- src/app/BUILD.gn | 2 + src/app/CommandHandler.cpp | 2 +- src/app/InteractionModelDelegate.h | 39 ++ src/app/InteractionModelEngine.cpp | 77 +++- src/app/InteractionModelEngine.h | 28 +- src/app/MessageDef/AttributeDataList.cpp | 5 + src/app/MessageDef/AttributeDataList.h | 2 + src/app/MessageDef/WriteRequest.cpp | 5 + src/app/MessageDef/WriteRequest.h | 3 + src/app/MessageDef/WriteResponse.cpp | 5 + src/app/MessageDef/WriteResponse.h | 7 + src/app/ReadClient.cpp | 2 - src/app/ReadHandler.cpp | 2 - src/app/WriteClient.cpp | 401 ++++++++++++++++++ src/app/WriteClient.h | 126 ++++++ src/app/WriteHandler.cpp | 307 ++++++++++++++ src/app/WriteHandler.h | 114 +++++ src/app/tests/BUILD.gn | 1 + src/app/tests/TestWriteInteraction.cpp | 307 ++++++++++++++ .../tests/integration/chip_im_initiator.cpp | 106 ++++- .../tests/integration/chip_im_responder.cpp | 17 + 21 files changed, 1534 insertions(+), 24 deletions(-) create mode 100644 src/app/WriteClient.cpp create mode 100644 src/app/WriteClient.h create mode 100644 src/app/WriteHandler.cpp create mode 100644 src/app/WriteHandler.h create mode 100644 src/app/tests/TestWriteInteraction.cpp diff --git a/src/app/BUILD.gn b/src/app/BUILD.gn index 199c996a73850a..fc914aaf386882 100644 --- a/src/app/BUILD.gn +++ b/src/app/BUILD.gn @@ -90,6 +90,8 @@ static_library("app") { "MessageDef/WriteResponse.cpp", "ReadClient.cpp", "ReadHandler.cpp", + "WriteClient.cpp", + "WriteHandler.cpp", "decoder.cpp", "encoder-common.cpp", "reporting/Engine.cpp", diff --git a/src/app/CommandHandler.cpp b/src/app/CommandHandler.cpp index 869b9007b58044..7ff93cf3cd2884 100644 --- a/src/app/CommandHandler.cpp +++ b/src/app/CommandHandler.cpp @@ -47,7 +47,7 @@ void CommandHandler::OnMessageReceived(Messaging::ExchangeContext * ec, const Pa err = ProcessCommandMessage(std::move(payload), CommandRoleId::HandlerId); SuccessOrExit(err); - SendCommandResponse(); + err = SendCommandResponse(); exit: ChipLogFunctError(err); diff --git a/src/app/InteractionModelDelegate.h b/src/app/InteractionModelDelegate.h index 3598c58b685706..e9f8165643e801 100644 --- a/src/app/InteractionModelDelegate.h +++ b/src/app/InteractionModelDelegate.h @@ -23,6 +23,7 @@ #pragma once +#include #include #include #include @@ -33,6 +34,7 @@ namespace chip { namespace app { class ReadClient; +class WriteClient; class CommandSender; /** @@ -134,6 +136,43 @@ class InteractionModelDelegate return CHIP_ERROR_NOT_IMPLEMENTED; } + /** + * Notification that a WriteClient has received an Write Response containing a status code. + */ + virtual CHIP_ERROR WriteResponseStatus(const WriteClient * apWriteClient, + const Protocols::SecureChannel::GeneralStatusCode aGeneralCode, + const uint32_t aProtocolId, const uint16_t aProtocolCode, + AttributePathParams & aAttributePathParams, uint8_t aCommandIndex) + { + return CHIP_ERROR_NOT_IMPLEMENTED; + } + + /** + * Notification that a Write Response has already been processed. + */ + virtual CHIP_ERROR WriteResponseProcessed(const WriteClient * apWriteClient) { return CHIP_ERROR_NOT_IMPLEMENTED; } + + /** + * Notification that a Write Client has received an Write Response and fails to process a attribute data element in that + * write response + */ + virtual CHIP_ERROR WriteResponseProtocolError(const WriteClient * apWriteClient, uint8_t aAttributeIndex) + { + return CHIP_ERROR_NOT_IMPLEMENTED; + } + + /** + * Notification that a write client encountered an asynchronous failure. + * @param[in] aCWriteClient write interactions + * @param[in] aError A error that could be CHIP_ERROR_TIMEOUT when write client fails to receive, or other error when + * fail to process write response. + * @retval # CHIP_ERROR_NOT_IMPLEMENTED if not implemented + */ + virtual CHIP_ERROR WriteResponseError(const WriteClient * apWriteClient, CHIP_ERROR aError) + { + return CHIP_ERROR_NOT_IMPLEMENTED; + } + virtual ~InteractionModelDelegate() = default; }; diff --git a/src/app/InteractionModelEngine.cpp b/src/app/InteractionModelEngine.cpp index 2ecb9915a71917..599acdd0080787 100644 --- a/src/app/InteractionModelEngine.cpp +++ b/src/app/InteractionModelEngine.cpp @@ -98,11 +98,28 @@ void InteractionModelEngine::Shutdown() } } + for (auto & writeClient : mWriteClients) + { + if (!writeClient.IsFree()) + { + writeClient.Shutdown(); + } + } + + for (auto & writeHandler : mWriteHandlers) + { + if (!writeHandler.IsFree()) + { + writeHandler.Shutdown(); + } + } + for (uint32_t index = 0; index < IM_SERVER_MAX_NUM_PATH_GROUPS; index++) { mClusterInfoPool[index].mpNext = nullptr; mClusterInfoPool[index].ClearDirty(); } + mpNextAvailableClusterInfo = nullptr; } @@ -151,6 +168,25 @@ CHIP_ERROR InteractionModelEngine::NewReadClient(ReadClient ** const apReadClien return err; } +CHIP_ERROR InteractionModelEngine::NewWriteClient(WriteClient ** const apWriteClient) +{ + *apWriteClient = nullptr; + + for (auto & writeClient : mWriteClients) + { + if (!writeClient.IsFree()) + { + continue; + } + + ReturnErrorOnFailure(writeClient.Init(mpExchangeMgr, mpDelegate)); + *apWriteClient = &writeClient; + return CHIP_NO_ERROR; + } + + return CHIP_ERROR_NO_MEMORY; +} + void InteractionModelEngine::OnUnknownMsgType(Messaging::ExchangeContext * apExchangeContext, const PacketHeader & aPacketHeader, const PayloadHeader & aPayloadHeader, System::PacketBufferHandle && aPayload) { @@ -230,6 +266,35 @@ void InteractionModelEngine::OnReadRequest(Messaging::ExchangeContext * apExchan } } +void InteractionModelEngine::OnWriteRequest(Messaging::ExchangeContext * apExchangeContext, const PacketHeader & aPacketHeader, + const PayloadHeader & aPayloadHeader, System::PacketBufferHandle && aPayload) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + ChipLogDetail(DataManagement, "Receive Write request"); + + for (auto & writeHandler : mWriteHandlers) + { + if (writeHandler.IsFree()) + { + err = writeHandler.Init(mpDelegate); + SuccessOrExit(err); + err = writeHandler.OnWriteRequest(apExchangeContext, std::move(aPayload)); + SuccessOrExit(err); + apExchangeContext = nullptr; + break; + } + } + +exit: + ChipLogFunctError(err); + + if (nullptr != apExchangeContext) + { + apExchangeContext->Abort(); + } +} + void InteractionModelEngine::OnMessageReceived(Messaging::ExchangeContext * apExchangeContext, const PacketHeader & aPacketHeader, const PayloadHeader & aPayloadHeader, System::PacketBufferHandle && aPayload) { @@ -242,6 +307,10 @@ void InteractionModelEngine::OnMessageReceived(Messaging::ExchangeContext * apEx { OnReadRequest(apExchangeContext, aPacketHeader, aPayloadHeader, std::move(aPayload)); } + else if (aPayloadHeader.HasMessageType(Protocols::InteractionModel::MsgType::WriteRequest)) + { + OnWriteRequest(apExchangeContext, aPacketHeader, aPayloadHeader, std::move(aPayload)); + } else { OnUnknownMsgType(apExchangeContext, aPacketHeader, aPayloadHeader, std::move(aPayload)); @@ -267,7 +336,8 @@ CHIP_ERROR __attribute__((weak)) ReadSingleClusterData(ClusterInfo & aClusterInf return CHIP_NO_ERROR; } -CHIP_ERROR __attribute__((weak)) WriteSingleClusterData(ClusterInfo & aClusterInfo, TLV::TLVReader & aReader) +CHIP_ERROR __attribute__((weak)) +WriteSingleClusterData(ClusterInfo & aClusterInfo, TLV::TLVReader & aReader, WriteHandler * apWriteHandler) { ChipLogDetail(DataManagement, "Received Cluster Attribute: Cluster=%" PRIx16 " NodeId=0x" ChipLogFormatX64 " Endpoint=%" PRIx8 @@ -284,6 +354,11 @@ uint16_t InteractionModelEngine::GetReadClientArrayIndex(const ReadClient * cons return static_cast(apReadClient - mReadClients); } +uint16_t InteractionModelEngine::GetWriteClientArrayIndex(const WriteClient * const apWriteClient) const +{ + return static_cast(apWriteClient - mWriteClients); +} + void InteractionModelEngine::ReleaseClusterInfoList(ClusterInfo *& aClusterInfo) { ClusterInfo * lastClusterInfo = aClusterInfo; diff --git a/src/app/InteractionModelEngine.h b/src/app/InteractionModelEngine.h index e5307223282ae5..f950f8cc064f25 100644 --- a/src/app/InteractionModelEngine.h +++ b/src/app/InteractionModelEngine.h @@ -44,6 +44,8 @@ #include #include #include +#include +#include #include #include @@ -54,6 +56,8 @@ #define CHIP_MAX_NUM_READ_HANDLER 1 #define CHIP_MAX_REPORTS_IN_FLIGHT 1 #define IM_SERVER_MAX_NUM_PATH_GROUPS 8 +#define CHIP_MAX_NUM_WRITE_CLIENT 1 +#define CHIP_MAX_NUM_WRITE_HANDLER 1 namespace chip { namespace app { @@ -122,6 +126,17 @@ class InteractionModelEngine : public Messaging::ExchangeDelegate */ CHIP_ERROR NewReadClient(ReadClient ** const apReadClient); + /** + * Retrieve a WriteClient that the SDK consumer can use to send do a write. If the call succeeds, the consumer + * is responsible for calling Shutdown() on the WriteClient once it's done using it. + * + * @param[out] apWriteClient A pointer to the WriteClient object. + * + * @retval #CHIP_ERROR_INCORRECT_STATE If there is no WriteClient available + * @retval #CHIP_NO_ERROR On success. + */ + CHIP_ERROR NewWriteClient(WriteClient ** const apWriteClient); + /** * Get read client index in mReadClients * @@ -131,6 +146,8 @@ class InteractionModelEngine : public Messaging::ExchangeDelegate */ uint16_t GetReadClientArrayIndex(const ReadClient * const apReadClient) const; + uint16_t GetWriteClientArrayIndex(const WriteClient * const apWriteClient) const; + reporting::Engine & GetReportingEngine() { return mReportingEngine; } void ReleaseClusterInfoList(ClusterInfo *& aClusterInfo); @@ -153,12 +170,21 @@ class InteractionModelEngine : public Messaging::ExchangeDelegate void OnReadRequest(Messaging::ExchangeContext * apExchangeContext, const PacketHeader & aPacketHeader, const PayloadHeader & aPayloadHeader, System::PacketBufferHandle && aPayload); + /** + * Called when Interaction Model receives a Write Request message. Errors processing + * the Write Request are handled entirely within this function. + */ + void OnWriteRequest(Messaging::ExchangeContext * apExchangeContext, const PacketHeader & aPacketHeader, + const PayloadHeader & aPayloadHeader, System::PacketBufferHandle && aPayload); + Messaging::ExchangeManager * mpExchangeMgr = nullptr; InteractionModelDelegate * mpDelegate = nullptr; CommandHandler mCommandHandlerObjs[CHIP_MAX_NUM_COMMAND_HANDLER]; CommandSender mCommandSenderObjs[CHIP_MAX_NUM_COMMAND_SENDER]; ReadClient mReadClients[CHIP_MAX_NUM_READ_CLIENT]; ReadHandler mReadHandlers[CHIP_MAX_NUM_READ_HANDLER]; + WriteClient mWriteClients[CHIP_MAX_NUM_WRITE_CLIENT]; + WriteHandler mWriteHandlers[CHIP_MAX_NUM_WRITE_HANDLER]; reporting::Engine mReportingEngine; ClusterInfo mClusterInfoPool[IM_SERVER_MAX_NUM_PATH_GROUPS]; ClusterInfo * mpNextAvailableClusterInfo = nullptr; @@ -179,6 +205,6 @@ void DispatchSingleClusterCommand(chip::ClusterId aClusterId, chip::CommandId aC */ bool ServerClusterCommandExists(chip::ClusterId aClusterId, chip::CommandId aCommandId, chip::EndpointId aEndPointId); CHIP_ERROR ReadSingleClusterData(ClusterInfo & aClusterInfo, TLV::TLVWriter & aWriter); -CHIP_ERROR WriteSingleClusterData(ClusterInfo & aClusterInfo, TLV::TLVReader & aReader); +CHIP_ERROR WriteSingleClusterData(ClusterInfo & aClusterInfo, TLV::TLVReader & aReader, WriteHandler * apWriteHandler); } // namespace app } // namespace chip diff --git a/src/app/MessageDef/AttributeDataList.cpp b/src/app/MessageDef/AttributeDataList.cpp index ca94a7c5233f0a..e74a9ad15265e0 100644 --- a/src/app/MessageDef/AttributeDataList.cpp +++ b/src/app/MessageDef/AttributeDataList.cpp @@ -102,6 +102,11 @@ AttributeDataElement::Builder & AttributeDataList::Builder::CreateAttributeDataE return mAttributeDataElementBuilder; } +AttributeDataElement::Builder & AttributeDataList::Builder::GetAttributeDataElementBuilder() +{ + return mAttributeDataElementBuilder; +} + AttributeDataList::Builder & AttributeDataList::Builder::EndOfAttributeDataList() { EndOfContainer(); diff --git a/src/app/MessageDef/AttributeDataList.h b/src/app/MessageDef/AttributeDataList.h index ba2c8cad65df9f..c917503e5ef5c6 100644 --- a/src/app/MessageDef/AttributeDataList.h +++ b/src/app/MessageDef/AttributeDataList.h @@ -66,6 +66,8 @@ class Builder : public ListBuilder */ AttributeDataElement::Builder & CreateAttributeDataElementBuilder(); + AttributeDataElement::Builder & GetAttributeDataElementBuilder(); + /** * @brief Mark the end of this AttributeDataList * diff --git a/src/app/MessageDef/WriteRequest.cpp b/src/app/MessageDef/WriteRequest.cpp index df99ee21805dce..accdd7e3a4a536 100644 --- a/src/app/MessageDef/WriteRequest.cpp +++ b/src/app/MessageDef/WriteRequest.cpp @@ -243,6 +243,11 @@ WriteRequest::Builder & WriteRequest::Builder::MoreChunkedMessages(const bool aM return *this; } +AttributeDataList::Builder & WriteRequest::Builder::GetAttributeDataListBuilder() +{ + return mAttributeDataListBuilder; +} + WriteRequest::Builder & WriteRequest::Builder::EndOfWriteRequest() { EndOfContainer(); diff --git a/src/app/MessageDef/WriteRequest.h b/src/app/MessageDef/WriteRequest.h index f32563a2c6791f..35ef752da86c42 100644 --- a/src/app/MessageDef/WriteRequest.h +++ b/src/app/MessageDef/WriteRequest.h @@ -148,6 +148,9 @@ class Builder : public chip::app::Builder * @return A reference to *this */ WriteRequest::Builder & MoreChunkedMessages(const bool aMoreChunkedMessages); + + AttributeDataList::Builder & GetAttributeDataListBuilder(); + /** * @brief Mark the end of this WriteRequest * diff --git a/src/app/MessageDef/WriteResponse.cpp b/src/app/MessageDef/WriteResponse.cpp index 100d5d7a4d967d..b98426be80c87f 100644 --- a/src/app/MessageDef/WriteResponse.cpp +++ b/src/app/MessageDef/WriteResponse.cpp @@ -139,6 +139,11 @@ AttributeStatusList::Builder & WriteResponse::Builder::CreateAttributeStatusList return mAttributeStatusListBuilder; } +AttributeStatusList::Builder & WriteResponse::Builder::GetAttributeStatusListBuilder() +{ + return mAttributeStatusListBuilder; +} + WriteResponse::Builder & WriteResponse::Builder::EndOfWriteResponse() { EndOfContainer(); diff --git a/src/app/MessageDef/WriteResponse.h b/src/app/MessageDef/WriteResponse.h index 4806744f06b166..6ff18a27043454 100644 --- a/src/app/MessageDef/WriteResponse.h +++ b/src/app/MessageDef/WriteResponse.h @@ -95,6 +95,13 @@ class Builder : public chip::app::Builder */ AttributeStatusList::Builder & CreateAttributeStatusListBuilder(); + /** + * @brief Get reference to AttributeStatusList::Builder + * + * @return A reference to AttributeStatusList::Builder + */ + AttributeStatusList::Builder & GetAttributeStatusListBuilder(); + /** * @brief Mark the end of this WriteResponse * diff --git a/src/app/ReadClient.cpp b/src/app/ReadClient.cpp index d1c139ad0e7529..11a4771dce9989 100644 --- a/src/app/ReadClient.cpp +++ b/src/app/ReadClient.cpp @@ -379,8 +379,6 @@ CHIP_ERROR ReadClient::ProcessAttributeDataList(TLV::TLVReader & aAttributeDataL err = element.GetData(&dataReader); SuccessOrExit(err); - err = WriteSingleClusterData(clusterInfo, dataReader); - SuccessOrExit(err); } if (CHIP_END_OF_TLV == err) diff --git a/src/app/ReadHandler.cpp b/src/app/ReadHandler.cpp index d7615483346455..5e41a8b9258cd2 100644 --- a/src/app/ReadHandler.cpp +++ b/src/app/ReadHandler.cpp @@ -78,9 +78,7 @@ CHIP_ERROR ReadHandler::OnReadRequest(Messaging::ExchangeContext * apExchangeCon mpExchangeCtx = apExchangeContext; err = ProcessReadRequest(std::move(aPayload)); - SuccessOrExit(err); -exit: if (err != CHIP_NO_ERROR) { ChipLogFunctError(err); diff --git a/src/app/WriteClient.cpp b/src/app/WriteClient.cpp new file mode 100644 index 00000000000000..8b85f854644483 --- /dev/null +++ b/src/app/WriteClient.cpp @@ -0,0 +1,401 @@ +/* + * + * 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. + */ + +/** + * @file + * This file defines the initiator side of a CHIP Write Interaction. + * + */ + +#include +#include + +namespace chip { +namespace app { + +CHIP_ERROR WriteClient::Init(Messaging::ExchangeManager * apExchangeMgr, InteractionModelDelegate * apDelegate) +{ + VerifyOrReturnError(apExchangeMgr != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(mpExchangeMgr == nullptr, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mpExchangeCtx == nullptr, CHIP_ERROR_INCORRECT_STATE); + + AttributeDataList::Builder attributeDataListBuilder; + System::PacketBufferHandle packet = System::PacketBufferHandle::New(chip::app::kMaxSecureSduLengthBytes); + VerifyOrReturnError(!packet.IsNull(), CHIP_ERROR_NO_MEMORY); + + mMessageWriter.Init(std::move(packet)); + + ReturnErrorOnFailure(mWriteRequestBuilder.Init(&mMessageWriter)); + + attributeDataListBuilder = mWriteRequestBuilder.CreateAttributeDataListBuilder(); + ReturnErrorOnFailure(attributeDataListBuilder.GetError()); + + ClearExistingExchangeContext(); + mpExchangeMgr = apExchangeMgr; + mpDelegate = apDelegate; + mAttributeStatusIndex = 0; + MoveToState(State::Initialized); + + return CHIP_NO_ERROR; +} + +void WriteClient::Shutdown() +{ + VerifyOrReturn(mState != State::Uninitialized); + mMessageWriter.Reset(); + + ClearExistingExchangeContext(); + + mpExchangeMgr = nullptr; + mpDelegate = nullptr; + mAttributeStatusIndex = 0; + ClearState(); +} + +void WriteClient::ClearExistingExchangeContext() +{ + // Discard any existing exchange context. Effectively we can only have one Echo exchange with + // a single node at any one time. + if (mpExchangeCtx != nullptr) + { + mpExchangeCtx->Abort(); + mpExchangeCtx = nullptr; + } +} + +CHIP_ERROR WriteClient::ProcessWriteResponseMessage(System::PacketBufferHandle && payload) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + System::PacketBufferTLVReader reader; + TLV::TLVReader attributeStatusListReader; + WriteResponse::Parser writeResponse; + AttributeStatusList::Parser attributeStatusListParser; + + reader.Init(std::move(payload)); + err = reader.Next(); + SuccessOrExit(err); + + err = writeResponse.Init(reader); + SuccessOrExit(err); + +#if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK + err = writeResponse.CheckSchemaValidity(); + SuccessOrExit(err); +#endif + err = writeResponse.GetAttributeStatusList(&attributeStatusListParser); + SuccessOrExit(err); + + attributeStatusListParser.GetReader(&attributeStatusListReader); + + while (CHIP_NO_ERROR == (err = attributeStatusListReader.Next())) + { + VerifyOrExit(TLV::AnonymousTag == attributeStatusListReader.GetTag(), err = CHIP_ERROR_INVALID_TLV_TAG); + VerifyOrExit(TLV::kTLVType_Structure == attributeStatusListReader.GetType(), err = CHIP_ERROR_WRONG_TLV_TYPE); + + AttributeStatusElement::Parser element; + + err = element.Init(attributeStatusListReader); + SuccessOrExit(err); + + err = ProcessAttributeStatusElement(element); + SuccessOrExit(err); + } + + // if we have exhausted this container + if (CHIP_END_OF_TLV == err) + { + err = CHIP_NO_ERROR; + } + +exit: + ChipLogFunctError(err); + return err; +} + +CHIP_ERROR WriteClient::PrepareAttribute(const AttributePathParams & attributePathParams) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + AttributeDataElement::Builder attributeDataElement = + mWriteRequestBuilder.GetAttributeDataListBuilder().CreateAttributeDataElementBuilder(); + SuccessOrExit(attributeDataElement.GetError()); + err = ConstructAttributePath(attributePathParams, attributeDataElement); + +exit: + ChipLogFunctError(err); + return err; +} + +CHIP_ERROR WriteClient::FinishAttribute() +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + AttributeDataElement::Builder attributeDataElement = + mWriteRequestBuilder.GetAttributeDataListBuilder().GetAttributeDataElementBuilder(); + + // TODO: Add attribute version support + attributeDataElement.DataVersion(0); + attributeDataElement.EndOfAttributeDataElement(); + SuccessOrExit(err = attributeDataElement.GetError()); + MoveToState(State::AddAttribute); + +exit: + ChipLogFunctError(err); + return err; +} + +TLV::TLVWriter * WriteClient::GetAttributeDataElementTLVWriter() +{ + return mWriteRequestBuilder.GetAttributeDataListBuilder().GetAttributeDataElementBuilder().GetWriter(); +} + +CHIP_ERROR WriteClient::ConstructAttributePath(const AttributePathParams & aAttributePathParams, + AttributeDataElement::Builder aAttributeDataElement) +{ + AttributePath::Builder attributePath = aAttributeDataElement.CreateAttributePathBuilder(); + if (aAttributePathParams.mFlags.Has(AttributePathParams::Flags::kFieldIdValid)) + { + attributePath.FieldId(aAttributePathParams.mFieldId); + } + + if (aAttributePathParams.mFlags.Has(AttributePathParams::Flags::kListIndexValid)) + { + attributePath.ListIndex(aAttributePathParams.mListIndex); + } + + attributePath.NodeId(aAttributePathParams.mNodeId) + .ClusterId(aAttributePathParams.mClusterId) + .EndpointId(aAttributePathParams.mEndpointId) + .EndOfAttributePath(); + + return attributePath.GetError(); +} + +CHIP_ERROR WriteClient::FinalizeMessage(System::PacketBufferHandle & aPacket) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + AttributeDataList::Builder attributeDataListBuilder; + VerifyOrExit(mState == State::AddAttribute, err = CHIP_ERROR_INCORRECT_STATE); + attributeDataListBuilder = mWriteRequestBuilder.GetAttributeDataListBuilder().EndOfAttributeDataList(); + err = attributeDataListBuilder.GetError(); + SuccessOrExit(err); + + mWriteRequestBuilder.EndOfWriteRequest(); + err = mWriteRequestBuilder.GetError(); + SuccessOrExit(err); + + err = mMessageWriter.Finalize(&aPacket); + SuccessOrExit(err); + +exit: + ChipLogFunctError(err); + return err; +} + +const char * WriteClient::GetStateStr() const +{ +#if CHIP_DETAIL_LOGGING + switch (mState) + { + case State::Uninitialized: + return "Uninitialized"; + + case State::Initialized: + return "Initialized"; + + case State::AddAttribute: + return "AddAttribute"; + + case State::AwaitingResponse: + return "AwaitingResponse"; + } +#endif // CHIP_DETAIL_LOGGING + return "N/A"; +} + +void WriteClient::MoveToState(const State aTargetState) +{ + mState = aTargetState; + ChipLogDetail(DataManagement, "WriteClient moving to [%10.10s]", GetStateStr()); +} + +void WriteClient::ClearState() +{ + MoveToState(State::Uninitialized); +} + +CHIP_ERROR WriteClient::SendWriteRequest(NodeId aNodeId, Transport::AdminId aAdminId, SecureSessionHandle * apSecureSession) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + System::PacketBufferHandle packet; + + VerifyOrExit(mState == State::AddAttribute, err = CHIP_ERROR_INCORRECT_STATE); + + err = FinalizeMessage(packet); + SuccessOrExit(err); + + // Discard any existing exchange context. Effectively we can only have one exchange per WriteClient + // at any one time. + ClearExistingExchangeContext(); + + // Create a new exchange context. + // TODO: temprary create a SecureSessionHandle from node id, will be fix in PR 3602 + // TODO: Hard code keyID to 0 to unblock IM end-to-end test. Complete solution is tracked in issue:4451 + if (apSecureSession == nullptr) + { + mpExchangeCtx = mpExchangeMgr->NewContext({ aNodeId, 0, aAdminId }, this); + } + else + { + mpExchangeCtx = mpExchangeMgr->NewContext(*apSecureSession, this); + } + VerifyOrExit(mpExchangeCtx != nullptr, err = CHIP_ERROR_NO_MEMORY); + mpExchangeCtx->SetResponseTimeout(kImMessageTimeoutMsec); + + err = mpExchangeCtx->SendMessage( + Protocols::InteractionModel::MsgType::WriteRequest, std::move(packet), + Messaging::SendFlags(Messaging::SendMessageFlags::kExpectResponse).Set(Messaging::SendMessageFlags::kNoAutoRequestAck)); + SuccessOrExit(err); + MoveToState(State::AwaitingResponse); + +exit: + if (err != CHIP_NO_ERROR) + { + ClearExistingExchangeContext(); + } + ChipLogFunctError(err); + + return err; +} + +void WriteClient::OnMessageReceived(Messaging::ExchangeContext * apExchangeContext, const PacketHeader & aPacketHeader, + const PayloadHeader & aPayloadHeader, System::PacketBufferHandle && aPayload) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + // Assert that the exchange context matches the client's current context. + // This should never fail because even if SendWriteRequest is called + // back-to-back, the second call will call Close() on the first exchange, + // which clears the OnMessageReceived callback. + + VerifyOrDie(apExchangeContext == mpExchangeCtx); + + // Verify that the message is an Invoke Command Response. + // If not, close the exchange and free the payload. + if (!aPayloadHeader.HasMessageType(Protocols::InteractionModel::MsgType::WriteResponse)) + { + apExchangeContext->Close(); + mpExchangeCtx = nullptr; + ExitNow(); + } + + // Close the current exchange after receiving the response since the response message marks the + // end of conversation represented by the exchange. We should create an new exchange for a new + // conversation defined in Interaction Model protocol. + ClearExistingExchangeContext(); + + err = ProcessWriteResponseMessage(std::move(aPayload)); + +exit: + if (mpDelegate != nullptr) + { + if (err != CHIP_NO_ERROR) + { + mpDelegate->WriteResponseError(this, err); + } + else + { + mpDelegate->WriteResponseProcessed(this); + } + } + Shutdown(); +} + +void WriteClient::OnResponseTimeout(Messaging::ExchangeContext * apExchangeContext) +{ + ChipLogProgress(DataManagement, "Time out! failed to receive write response from Exchange: %d", + apExchangeContext->GetExchangeId()); + + if (mpDelegate != nullptr) + { + mpDelegate->WriteResponseError(this, CHIP_ERROR_TIMEOUT); + } + Shutdown(); +} + +CHIP_ERROR WriteClient::ProcessAttributeStatusElement(AttributeStatusElement::Parser & aAttributeStatusElement) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + AttributePath::Parser attributePath; + Protocols::SecureChannel::GeneralStatusCode generalCode = Protocols::SecureChannel::GeneralStatusCode::kSuccess; + uint32_t protocolId = 0; + uint16_t protocolCode = 0; + StatusElement::Parser statusElementParser; + AttributePathParams attributePathParams; + + mAttributeStatusIndex++; + err = aAttributeStatusElement.GetAttributePath(&attributePath); + SuccessOrExit(err); + err = attributePath.GetNodeId(&(attributePathParams.mNodeId)); + SuccessOrExit(err); + err = attributePath.GetClusterId(&(attributePathParams.mClusterId)); + SuccessOrExit(err); + err = attributePath.GetEndpointId(&(attributePathParams.mEndpointId)); + SuccessOrExit(err); + + err = attributePath.GetFieldId(&(attributePathParams.mFieldId)); + if (CHIP_NO_ERROR == err) + { + attributePathParams.mFlags.Set(AttributePathParams::Flags::kFieldIdValid); + } + else if (CHIP_END_OF_TLV == err) + { + err = CHIP_NO_ERROR; + } + SuccessOrExit(err); + + err = attributePath.GetListIndex(&(attributePathParams.mListIndex)); + if (CHIP_NO_ERROR == err) + { + VerifyOrExit(attributePathParams.mFlags.Has(AttributePathParams::Flags::kFieldIdValid), + err = CHIP_ERROR_IM_MALFORMED_ATTRIBUTE_PATH); + attributePathParams.mFlags.Set(AttributePathParams::Flags::kListIndexValid); + } + + err = aAttributeStatusElement.GetStatusElement(&(statusElementParser)); + if (CHIP_NO_ERROR == err) + { + err = statusElementParser.DecodeStatusElement(&generalCode, &protocolId, &protocolCode); + SuccessOrExit(err); + if (mpDelegate != nullptr) + { + mpDelegate->WriteResponseStatus(this, generalCode, protocolId, protocolCode, attributePathParams, + mAttributeStatusIndex); + } + } + +exit: + ChipLogFunctError(err); + if (err != CHIP_NO_ERROR && mpDelegate != nullptr) + { + mpDelegate->WriteResponseProtocolError(this, mAttributeStatusIndex); + } + return err; +} + +} // namespace app +} // namespace chip diff --git a/src/app/WriteClient.h b/src/app/WriteClient.h new file mode 100644 index 00000000000000..cdd651ed0b4113 --- /dev/null +++ b/src/app/WriteClient.h @@ -0,0 +1,126 @@ +/* + * + * 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. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace chip { +namespace app { +/** + * @brief The read client represents the initiator side of a Write Interaction, and is responsible + * for generating one Write Request for a particular set of attributes, and handling the Write response. + * + */ +class WriteClient : public Messaging::ExchangeDelegate +{ +public: + /** + * Shutdown the WriteClient. This terminates this instance + * of the object and releases all held resources. + */ + void Shutdown(); + + /** + * Finalize Write Request Message TLV Builder and retrieve final data from tlv builder for later sending + */ + CHIP_ERROR FinalizeMessage(System::PacketBufferHandle & aPacket); + + /** + * Send a Write Request. There can be one Write Request outstanding on a given WriteClient. + * If SendWriteRequest returns success, no more Write Requests can be sent on this WriteClient + * until the corresponding InteractionModelDelegate::WriteResponseProcessed or InteractionModelDelegate::WriteResponseError + * call happens with guarantee. + */ + CHIP_ERROR SendWriteRequest(NodeId aNodeId, Transport::AdminId aAdminId, SecureSessionHandle * apSecureSession); + + CHIP_ERROR PrepareAttribute(const AttributePathParams & attributePathParams); + CHIP_ERROR FinishAttribute(); + TLV::TLVWriter * GetAttributeDataElementTLVWriter(); + +private: + friend class TestWriteInteraction; + friend class InteractionModelEngine; + + enum class State + { + Uninitialized = 0, // The client has not been initialized + Initialized, // The client has been initialized + AddAttribute, // The client has added attribute and ready for a SendWriteRequest + AwaitingResponse, // The client has sent out the write request message + }; + + /** + * Initialize the client object. Within the lifetime + * of this instance, this method is invoked once after object + * construction until a call to Shutdown is made to terminate the + * instance. + * + * @param[in] apExchangeMgr A pointer to the ExchangeManager object. + * @param[in] apDelegate InteractionModelDelegate set by application. + * @retval #CHIP_ERROR_INCORRECT_STATE incorrect state if it is already initialized + * @retval #CHIP_NO_ERROR On success. + */ + CHIP_ERROR Init(Messaging::ExchangeManager * apExchangeMgr, InteractionModelDelegate * apDelegate); + + virtual ~WriteClient() = default; + + void OnMessageReceived(Messaging::ExchangeContext * apExchangeContext, const PacketHeader & aPacketHeader, + const PayloadHeader & aPayloadHeader, System::PacketBufferHandle && aPayload) override; + void OnResponseTimeout(Messaging::ExchangeContext * apExchangeContext) override; + + /** + * Check if current write client is being used + */ + bool IsFree() const { return mState == State::Uninitialized; }; + + void MoveToState(const State aTargetState); + CHIP_ERROR ProcessWriteResponseMessage(System::PacketBufferHandle && payload); + CHIP_ERROR ProcessAttributeStatusElement(AttributeStatusElement::Parser & aAttributeStatusElement); + CHIP_ERROR ConstructAttributePath(const AttributePathParams & aAttributePathParams, + AttributeDataElement::Builder aAttributeDataElement); + void ClearExistingExchangeContext(); + const char * GetStateStr() const; + void ClearState(); + + Messaging::ExchangeManager * mpExchangeMgr = nullptr; + Messaging::ExchangeContext * mpExchangeCtx = nullptr; + InteractionModelDelegate * mpDelegate = nullptr; + State mState = State::Uninitialized; + System::PacketBufferTLVWriter mMessageWriter; + WriteRequest::Builder mWriteRequestBuilder; + uint8_t mAttributeStatusIndex = 0; + intptr_t mAppIdentifier = 0; +}; + +} // namespace app +} // namespace chip diff --git a/src/app/WriteHandler.cpp b/src/app/WriteHandler.cpp new file mode 100644 index 00000000000000..d3ae02b5645855 --- /dev/null +++ b/src/app/WriteHandler.cpp @@ -0,0 +1,307 @@ +/* + * + * 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 + +namespace chip { +namespace app { +CHIP_ERROR WriteHandler::Init(InteractionModelDelegate * apDelegate) +{ + VerifyOrReturnError(apDelegate != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(mpExchangeCtx == nullptr, CHIP_ERROR_INCORRECT_STATE); + mpExchangeCtx = nullptr; + mpDelegate = apDelegate; + + System::PacketBufferHandle packet = System::PacketBufferHandle::New(chip::app::kMaxSecureSduLengthBytes); + VerifyOrReturnError(!packet.IsNull(), CHIP_ERROR_NO_MEMORY); + + mMessageWriter.Init(std::move(packet)); + ReturnErrorOnFailure(mWriteResponseBuilder.Init(&mMessageWriter)); + + AttributeStatusList::Builder attributeStatusListBuilder = mWriteResponseBuilder.CreateAttributeStatusListBuilder(); + ReturnErrorOnFailure(attributeStatusListBuilder.GetError()); + + MoveToState(State::Initialized); + + return CHIP_NO_ERROR; +} + +void WriteHandler::Shutdown() +{ + VerifyOrReturn(mState != State::Uninitialized); + mMessageWriter.Reset(); + ClearExistingExchangeContext(); + mpDelegate = nullptr; + ClearState(); +} + +void WriteHandler::ClearExistingExchangeContext() +{ + if (mpExchangeCtx != nullptr) + { + mpExchangeCtx->Close(); + mpExchangeCtx = nullptr; + } +} + +CHIP_ERROR WriteHandler::OnWriteRequest(Messaging::ExchangeContext * apExchangeContext, System::PacketBufferHandle aPayload) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + mpExchangeCtx = apExchangeContext; + + err = ProcessWriteRequest(std::move(aPayload)); + SuccessOrExit(err); + err = SendWriteResponse(); + +exit: + ChipLogFunctError(err); + Shutdown(); + return err; +} + +CHIP_ERROR WriteHandler::FinalizeMessage(System::PacketBufferHandle & packet) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + AttributeStatusList::Builder attributeStatusList; + VerifyOrExit(mState == State::AddAttributeStatusCode, err = CHIP_ERROR_INCORRECT_STATE); + attributeStatusList = mWriteResponseBuilder.GetAttributeStatusListBuilder().EndOfAttributeStatusList(); + err = attributeStatusList.GetError(); + SuccessOrExit(err); + + mWriteResponseBuilder.EndOfWriteResponse(); + err = mWriteResponseBuilder.GetError(); + SuccessOrExit(err); + + err = mMessageWriter.Finalize(&packet); + SuccessOrExit(err); + +exit: + ChipLogFunctError(err); + return err; +} + +CHIP_ERROR WriteHandler::SendWriteResponse() +{ + CHIP_ERROR err = CHIP_NO_ERROR; + System::PacketBufferHandle packet; + + VerifyOrExit(mState == State::AddAttributeStatusCode, err = CHIP_ERROR_INCORRECT_STATE); + + err = FinalizeMessage(packet); + SuccessOrExit(err); + + VerifyOrExit(mpExchangeCtx != nullptr, err = CHIP_ERROR_INCORRECT_STATE); + err = mpExchangeCtx->SendMessage(Protocols::InteractionModel::MsgType::WriteResponse, std::move(packet)); + SuccessOrExit(err); + + MoveToState(State::Sending); + +exit: + ChipLogFunctError(err); + return err; +} + +CHIP_ERROR WriteHandler::ProcessAttributeDataList(TLV::TLVReader & aAttributeDataListReader) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + while (CHIP_NO_ERROR == (err = aAttributeDataListReader.Next())) + { + chip::TLV::TLVReader dataReader; + AttributeDataElement::Parser element; + AttributePath::Parser attributePath; + ClusterInfo clusterInfo; + TLV::TLVReader reader = aAttributeDataListReader; + err = element.Init(reader); + SuccessOrExit(err); + + err = element.GetAttributePath(&attributePath); + SuccessOrExit(err); + + err = attributePath.GetNodeId(&(clusterInfo.mNodeId)); + SuccessOrExit(err); + + err = attributePath.GetEndpointId(&(clusterInfo.mEndpointId)); + SuccessOrExit(err); + + err = attributePath.GetClusterId(&(clusterInfo.mClusterId)); + SuccessOrExit(err); + + err = attributePath.GetFieldId(&(clusterInfo.mFieldId)); + if (CHIP_NO_ERROR == err) + { + clusterInfo.mFlags.Set(ClusterInfo::Flags::kFieldIdValid); + } + else if (CHIP_END_OF_TLV == err) + { + err = CHIP_NO_ERROR; + } + SuccessOrExit(err); + + err = attributePath.GetListIndex(&(clusterInfo.mListIndex)); + if (CHIP_NO_ERROR == err) + { + VerifyOrExit(clusterInfo.mFlags.Has(ClusterInfo::Flags::kFieldIdValid), err = CHIP_ERROR_IM_MALFORMED_ATTRIBUTE_PATH); + clusterInfo.mFlags.Set(ClusterInfo::Flags::kListIndexValid); + } + + err = element.GetData(&dataReader); + SuccessOrExit(err); + err = WriteSingleClusterData(clusterInfo, dataReader, this); + SuccessOrExit(err); + } + + if (CHIP_END_OF_TLV == err) + { + err = CHIP_NO_ERROR; + } + +exit: + ChipLogFunctError(err); + return err; +} + +CHIP_ERROR WriteHandler::ProcessWriteRequest(System::PacketBufferHandle && aPayload) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + System::PacketBufferTLVReader reader; + + WriteRequest::Parser writeRequestParser; + AttributeDataList::Parser attributeDataListParser; + TLV::TLVReader attributeDataListReader; + bool needSuppressResponse = false; + + reader.Init(std::move(aPayload)); + + err = reader.Next(); + SuccessOrExit(err); + + err = writeRequestParser.Init(reader); + SuccessOrExit(err); + +#if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK + err = writeRequestParser.CheckSchemaValidity(); + SuccessOrExit(err); +#endif + err = writeRequestParser.GetSuppressResponse(&needSuppressResponse); + if (err == CHIP_END_OF_TLV) + { + err = CHIP_NO_ERROR; + } + SuccessOrExit(err); + + err = writeRequestParser.GetAttributeDataList(&attributeDataListParser); + SuccessOrExit(err); + attributeDataListParser.GetReader(&attributeDataListReader); + err = ProcessAttributeDataList(attributeDataListReader); + +exit: + ChipLogFunctError(err); + return err; +} + +CHIP_ERROR WriteHandler::ConstructAttributePath(const AttributePathParams & aAttributePathParams, + AttributeStatusElement::Builder aAttributeStatusElement) +{ + AttributePath::Builder attributePath = aAttributeStatusElement.CreateAttributePathBuilder(); + if (aAttributePathParams.mFlags.Has(AttributePathParams::Flags::kFieldIdValid)) + { + attributePath.FieldId(aAttributePathParams.mFieldId); + } + + if (aAttributePathParams.mFlags.Has(AttributePathParams::Flags::kListIndexValid)) + { + attributePath.ListIndex(aAttributePathParams.mListIndex); + } + + attributePath.NodeId(aAttributePathParams.mNodeId) + .ClusterId(aAttributePathParams.mClusterId) + .EndpointId(aAttributePathParams.mEndpointId) + .EndOfAttributePath(); + + return attributePath.GetError(); +} + +CHIP_ERROR WriteHandler::AddAttributeStatusCode(const AttributePathParams & aAttributePathParams, + const Protocols::SecureChannel::GeneralStatusCode aGeneralCode, + const Protocols::Id aProtocolId, + const Protocols::InteractionModel::ProtocolCode aProtocolCode) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + StatusElement::Builder statusElementBuilder; + AttributeStatusElement::Builder attributeStatusElement = + mWriteResponseBuilder.GetAttributeStatusListBuilder().CreateAttributeStatusBuilder(); + err = attributeStatusElement.GetError(); + SuccessOrExit(err); + + err = ConstructAttributePath(aAttributePathParams, attributeStatusElement); + SuccessOrExit(err); + + statusElementBuilder = attributeStatusElement.CreateStatusElementBuilder(); + statusElementBuilder + .EncodeStatusElement(aGeneralCode, aProtocolId.ToFullyQualifiedSpecForm(), + Protocols::InteractionModel::ToUint16(aProtocolCode)) + .EndOfStatusElement(); + err = statusElementBuilder.GetError(); + SuccessOrExit(err); + + attributeStatusElement.EndOfAttributeStatusElement(); + err = attributeStatusElement.GetError(); + SuccessOrExit(err); + MoveToState(State::AddAttributeStatusCode); + +exit: + ChipLogFunctError(err); + return err; +} + +const char * WriteHandler::GetStateStr() const +{ +#if CHIP_DETAIL_LOGGING + switch (mState) + { + case State::Uninitialized: + return "Uninitialized"; + + case State::Initialized: + return "Initialized"; + + case State::AddAttributeStatusCode: + return "AddAttributeStatusCode"; + case State::Sending: + return "Sending"; + } +#endif // CHIP_DETAIL_LOGGING + return "N/A"; +} + +void WriteHandler::MoveToState(const State aTargetState) +{ + mState = aTargetState; + ChipLogDetail(DataManagement, "IM RH moving to [%s]", GetStateStr()); +} + +void WriteHandler::ClearState() +{ + MoveToState(State::Uninitialized); +} + +} // namespace app +} // namespace chip diff --git a/src/app/WriteHandler.h b/src/app/WriteHandler.h new file mode 100644 index 00000000000000..917e444f889dd9 --- /dev/null +++ b/src/app/WriteHandler.h @@ -0,0 +1,114 @@ +/* + * + * 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. + */ + +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace chip { +namespace app { +/** + * @brief The write handler is responsible for processing a write request and sending a write reply. + */ +class WriteHandler +{ +public: + /** + * Initialize the WriteHandler. Within the lifetime + * of this instance, this method is invoked once after object + * construction until a call to Shutdown is made to terminate the + * instance. + * + * @param[in] apDelegate InteractionModelDelegate set by application. + * + * @retval #CHIP_ERROR_INCORRECT_STATE If the state is not equal to + * kState_NotInitialized. + * @retval #CHIP_NO_ERROR On success. + */ + CHIP_ERROR Init(InteractionModelDelegate * apDelegate); + + /** + * Shut down the ReadHandler. This terminates this instance + * of the object and releases all held resources. + */ + void Shutdown(); + /** + * Process a write request. Parts of the processing may end up being asynchronous, but the WriteHandler + * guarantees that it will call Shutdown on itself when processing is done (including if OnWriteHandler + * returns an error). + * + * @param[in] apExchangeContext A pointer to the ExchangeContext. + * @param[in] aPayload A payload that has read request data + * + * @retval #Others If fails to process read request + * @retval #CHIP_NO_ERROR On success. + */ + CHIP_ERROR OnWriteRequest(Messaging::ExchangeContext * apExchangeContext, System::PacketBufferHandle aPayload); + + CHIP_ERROR SendWriteResponse(System::PacketBufferHandle aPayload); + + bool IsFree() const { return mState == State::Uninitialized; } + + virtual ~WriteHandler() = default; + + CHIP_ERROR ProcessAttributeDataList(TLV::TLVReader & aAttributeDataListReader); + + CHIP_ERROR AddAttributeStatusCode(const AttributePathParams & aAttributePathParams, + const Protocols::SecureChannel::GeneralStatusCode aGeneralCode, + const Protocols::Id aProtocolId, + const Protocols::InteractionModel::ProtocolCode aProtocolCode); + +private: + friend class TestWriteInteraction; + enum class State + { + Uninitialized = 0, // The handler has not been initialized + Initialized, // The handler has been initialized and is ready + AddAttributeStatusCode, // The handler has added attribute status code + Sending, // The handler has sent out the write response + }; + CHIP_ERROR ProcessWriteRequest(System::PacketBufferHandle && aPayload); + CHIP_ERROR FinalizeMessage(System::PacketBufferHandle & packet); + CHIP_ERROR SendWriteResponse(); + CHIP_ERROR ConstructAttributePath(const AttributePathParams & aAttributePathParams, + AttributeStatusElement::Builder aAttributeStatusElement); + + void MoveToState(const State aTargetState); + void ClearState(); + const char * GetStateStr() const; + void ClearExistingExchangeContext(); + + Messaging::ExchangeContext * mpExchangeCtx = nullptr; + InteractionModelDelegate * mpDelegate = nullptr; + WriteResponse::Builder mWriteResponseBuilder; + System::PacketBufferTLVWriter mMessageWriter; + State mState; +}; +} // namespace app +} // namespace chip diff --git a/src/app/tests/BUILD.gn b/src/app/tests/BUILD.gn index 72fe10d4fcaca0..d96a5087315ec1 100644 --- a/src/app/tests/BUILD.gn +++ b/src/app/tests/BUILD.gn @@ -31,6 +31,7 @@ chip_test_suite("tests") { "TestMessageDef.cpp", "TestReadInteraction.cpp", "TestReportingEngine.cpp", + "TestWriteInteraction.cpp", ] cflags = [ "-Wconversion" ] diff --git a/src/app/tests/TestWriteInteraction.cpp b/src/app/tests/TestWriteInteraction.cpp new file mode 100644 index 00000000000000..e550dae012fe6e --- /dev/null +++ b/src/app/tests/TestWriteInteraction.cpp @@ -0,0 +1,307 @@ +/* + * + * 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 +#include +#include +#include +#include +#include + +#include + +namespace { +chip::System::Layer gSystemLayer; +chip::SecureSessionMgr gSessionManager; +chip::Messaging::ExchangeManager gExchangeManager; +chip::TransportMgr gTransportManager; +chip::secure_channel::MessageCounterManager gMessageCounterManager; +chip::Transport::AdminId gAdminId = 0; +} // namespace +namespace chip { +namespace app { +class TestWriteInteraction +{ +public: + static void TestWriteClient(nlTestSuite * apSuite, void * apContext); + static void TestWriteHandler(nlTestSuite * apSuite, void * apContext); + +private: + static void AddAttributeDataElement(nlTestSuite * apSuite, void * apContext, WriteClient & aWriteClient); + static void AddAttributeStatus(nlTestSuite * apSuite, void * apContext, WriteHandler & aWriteHandler); + static void GenerateWriteRequest(nlTestSuite * apSuite, void * apContext, System::PacketBufferHandle & aPayload); + static void GenerateWriteResponse(nlTestSuite * apSuite, void * apContext, System::PacketBufferHandle & aPayload); +}; + +class TestExchangeDelegate : public Messaging::ExchangeDelegate +{ + void OnMessageReceived(Messaging::ExchangeContext * ec, const PacketHeader & packetHeader, const PayloadHeader & payloadHeader, + System::PacketBufferHandle && payload) override + {} + + void OnResponseTimeout(Messaging::ExchangeContext * ec) override {} +}; + +void TestWriteInteraction::AddAttributeDataElement(nlTestSuite * apSuite, void * apContext, WriteClient & aWriteClient) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + AttributePathParams attributePathParams; + attributePathParams.mNodeId = 1; + attributePathParams.mEndpointId = 2; + attributePathParams.mClusterId = 3; + attributePathParams.mFieldId = 4; + attributePathParams.mListIndex = 5; + attributePathParams.mFlags.Set(AttributePathParams::Flags::kFieldIdValid); + + err = aWriteClient.PrepareAttribute(attributePathParams); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + chip::TLV::TLVWriter * writer = aWriteClient.GetAttributeDataElementTLVWriter(); + + err = writer->PutBoolean(chip::TLV::ContextTag(1), true); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + err = aWriteClient.FinishAttribute(); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); +} + +void TestWriteInteraction::AddAttributeStatus(nlTestSuite * apSuite, void * apContext, WriteHandler & aWriteHandler) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + AttributePathParams attributePathParams; + attributePathParams.mNodeId = 1; + attributePathParams.mEndpointId = 2; + attributePathParams.mClusterId = 3; + attributePathParams.mFieldId = 4; + attributePathParams.mListIndex = 5; + attributePathParams.mFlags.Set(AttributePathParams::Flags::kFieldIdValid); + + err = aWriteHandler.AddAttributeStatusCode(attributePathParams, Protocols::SecureChannel::GeneralStatusCode::kSuccess, + Protocols::SecureChannel::Id, Protocols::InteractionModel::ProtocolCode::Success); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); +} + +void TestWriteInteraction::GenerateWriteRequest(nlTestSuite * apSuite, void * apContext, System::PacketBufferHandle & aPayload) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + System::PacketBufferTLVWriter writer; + writer.Init(std::move(aPayload)); + + WriteRequest::Builder writeRequestBuilder; + err = writeRequestBuilder.Init(&writer); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + AttributeDataList::Builder attributeDataListBuilder = writeRequestBuilder.CreateAttributeDataListBuilder(); + NL_TEST_ASSERT(apSuite, attributeDataListBuilder.GetError() == CHIP_NO_ERROR); + AttributeDataElement::Builder attributeDataElementBuilder = attributeDataListBuilder.CreateAttributeDataElementBuilder(); + NL_TEST_ASSERT(apSuite, attributeDataElementBuilder.GetError() == CHIP_NO_ERROR); + + AttributePath::Builder attributePathBuilder = attributeDataElementBuilder.CreateAttributePathBuilder(); + NL_TEST_ASSERT(apSuite, attributePathBuilder.GetError() == CHIP_NO_ERROR); + attributePathBuilder.NodeId(1).EndpointId(2).ClusterId(3).FieldId(4).ListIndex(5).EndOfAttributePath(); + err = attributePathBuilder.GetError(); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + // Construct attribute data + { + chip::TLV::TLVWriter * pWriter = attributeDataElementBuilder.GetWriter(); + chip::TLV::TLVType dummyType = chip::TLV::kTLVType_NotSpecified; + err = pWriter->StartContainer(chip::TLV::ContextTag(AttributeDataElement::kCsTag_Data), chip::TLV::kTLVType_Structure, + dummyType); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + err = pWriter->PutBoolean(chip::TLV::ContextTag(1), true); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + err = pWriter->EndContainer(dummyType); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + } + + attributeDataElementBuilder.DataVersion(0); + attributeDataElementBuilder.EndOfAttributeDataElement(); + NL_TEST_ASSERT(apSuite, attributeDataElementBuilder.GetError() == CHIP_NO_ERROR); + + attributeDataListBuilder.EndOfAttributeDataList(); + NL_TEST_ASSERT(apSuite, attributeDataListBuilder.GetError() == CHIP_NO_ERROR); + writeRequestBuilder.EndOfWriteRequest(); + NL_TEST_ASSERT(apSuite, writeRequestBuilder.GetError() == CHIP_NO_ERROR); + + err = writer.Finalize(&aPayload); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); +} + +void TestWriteInteraction::GenerateWriteResponse(nlTestSuite * apSuite, void * apContext, System::PacketBufferHandle & aPayload) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + System::PacketBufferTLVWriter writer; + writer.Init(std::move(aPayload)); + + WriteResponse::Builder writeResponseBuilder; + err = writeResponseBuilder.Init(&writer); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + AttributeStatusList::Builder attributeStatusListBuilder = writeResponseBuilder.CreateAttributeStatusListBuilder(); + NL_TEST_ASSERT(apSuite, attributeStatusListBuilder.GetError() == CHIP_NO_ERROR); + AttributeStatusElement::Builder attributeStatusElementBuilder = attributeStatusListBuilder.CreateAttributeStatusBuilder(); + NL_TEST_ASSERT(apSuite, attributeStatusElementBuilder.GetError() == CHIP_NO_ERROR); + + AttributePath::Builder attributePathBuilder = attributeStatusElementBuilder.CreateAttributePathBuilder(); + NL_TEST_ASSERT(apSuite, attributePathBuilder.GetError() == CHIP_NO_ERROR); + attributePathBuilder.NodeId(1).EndpointId(2).ClusterId(3).FieldId(4).ListIndex(5).EndOfAttributePath(); + err = attributePathBuilder.GetError(); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + StatusElement::Builder statusElementBuilder = attributeStatusElementBuilder.CreateStatusElementBuilder(); + NL_TEST_ASSERT(apSuite, statusElementBuilder.GetError() == CHIP_NO_ERROR); + statusElementBuilder.EncodeStatusElement(chip::Protocols::SecureChannel::GeneralStatusCode::kFailure, 2, 3) + .EndOfStatusElement(); + err = statusElementBuilder.GetError(); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + attributeStatusElementBuilder.EndOfAttributeStatusElement(); + NL_TEST_ASSERT(apSuite, attributeStatusElementBuilder.GetError() == CHIP_NO_ERROR); + + attributeStatusListBuilder.EndOfAttributeStatusList(); + NL_TEST_ASSERT(apSuite, attributeStatusListBuilder.GetError() == CHIP_NO_ERROR); + writeResponseBuilder.EndOfWriteResponse(); + NL_TEST_ASSERT(apSuite, writeResponseBuilder.GetError() == CHIP_NO_ERROR); + + err = writer.Finalize(&aPayload); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); +} + +void TestWriteInteraction::TestWriteClient(nlTestSuite * apSuite, void * apContext) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + app::WriteClient writeClient; + + chip::app::InteractionModelDelegate delegate; + System::PacketBufferHandle buf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize); + err = writeClient.Init(&gExchangeManager, &delegate); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + AddAttributeDataElement(apSuite, apContext, writeClient); + + err = writeClient.SendWriteRequest(kTestDeviceNodeId, gAdminId, nullptr); + NL_TEST_ASSERT(apSuite, err == CHIP_ERROR_NOT_CONNECTED); + + GenerateWriteResponse(apSuite, apContext, buf); + + err = writeClient.ProcessWriteResponseMessage(std::move(buf)); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + writeClient.Shutdown(); +} + +void TestWriteInteraction::TestWriteHandler(nlTestSuite * apSuite, void * apContext) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + app::WriteHandler writeHandler; + + chip::app::InteractionModelDelegate IMdelegate; + System::PacketBufferHandle buf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize); + err = writeHandler.Init(&IMdelegate); + + GenerateWriteRequest(apSuite, apContext, buf); + + err = writeHandler.ProcessWriteRequest(std::move(buf)); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + AddAttributeStatus(apSuite, apContext, writeHandler); + + writeHandler.mpExchangeCtx = gExchangeManager.NewContext({ 0, 0, 0 }, nullptr); + TestExchangeDelegate delegate; + writeHandler.mpExchangeCtx->SetDelegate(&delegate); + err = writeHandler.SendWriteResponse(); + NL_TEST_ASSERT(apSuite, err == CHIP_ERROR_NOT_CONNECTED); + + writeHandler.Shutdown(); +} +} // namespace app +} // namespace chip + +namespace { + +void InitializeChip(nlTestSuite * apSuite) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + chip::Optional peer(chip::Transport::Type::kUndefined); + chip::Transport::AdminPairingTable admins; + chip::Transport::AdminPairingInfo * adminInfo = admins.AssignAdminId(gAdminId, chip::kTestDeviceNodeId); + + NL_TEST_ASSERT(apSuite, adminInfo != nullptr); + + err = chip::Platform::MemoryInit(); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + gSystemLayer.Init(nullptr); + + err = gSessionManager.Init(chip::kTestDeviceNodeId, &gSystemLayer, &gTransportManager, &admins, &gMessageCounterManager); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + err = gExchangeManager.Init(&gSessionManager); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + err = gMessageCounterManager.Init(&gExchangeManager); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); +} + +/** + * Test Suite. It lists all the test functions. + */ + +// clang-format off +const nlTest sTests[] = +{ + NL_TEST_DEF("CheckWriteClient", chip::app::TestWriteInteraction::TestWriteClient), + NL_TEST_DEF("CheckWriteHandler", chip::app::TestWriteInteraction::TestWriteHandler), + NL_TEST_SENTINEL() +}; +// clang-format on +} // namespace + +int TestWriteInteraction() +{ + // clang-format off + nlTestSuite theSuite = + { + "TestWriteInteraction", + &sTests[0], + nullptr, + nullptr + }; + // clang-format on + + InitializeChip(&theSuite); + + nlTestRunner(&theSuite, nullptr); + + return (nlTestRunnerStats(&theSuite)); +} + +CHIP_REGISTER_TEST_SUITE(TestWriteInteraction) diff --git a/src/app/tests/integration/chip_im_initiator.cpp b/src/app/tests/integration/chip_im_initiator.cpp index 22a48120ad14f4..68e430ffedca2e 100644 --- a/src/app/tests/integration/chip_im_initiator.cpp +++ b/src/app/tests/integration/chip_im_initiator.cpp @@ -47,6 +47,7 @@ namespace { constexpr size_t kMaxCommandMessageCount = 3; constexpr size_t kTotalFailureCommandMessageCount = 1; constexpr size_t kMaxReadMessageCount = 3; +constexpr size_t kMaxWriteMessageCount = 3; constexpr int32_t gMessageIntervalSeconds = 1; constexpr chip::Transport::AdminId gAdminId = 0; @@ -65,12 +66,18 @@ uint64_t gCommandCount = 0; // Count of the number of CommandResponses received. uint64_t gCommandRespCount = 0; -// Count of the number of CommandRequests sent. +// Count of the number of ReadRequests sent. uint64_t gReadCount = 0; -// Count of the number of CommandResponses received. +// Count of the number of ReadResponses received. uint64_t gReadRespCount = 0; +// Count of the number of WriteRequests sent. +uint64_t gWriteCount = 0; + +// Count of the number of WriteResponses received. +uint64_t gWriteRespCount = 0; + // Whether the last command successed. enum class TestCommandResult : uint8_t { @@ -170,7 +177,7 @@ CHIP_ERROR SendBadCommandRequest(chip::app::CommandSender * commandSender) return err; } -CHIP_ERROR SendReadRequest(void) +CHIP_ERROR SendReadRequest() { CHIP_ERROR err = CHIP_NO_ERROR; chip::EventNumber number = 0; @@ -197,6 +204,40 @@ CHIP_ERROR SendReadRequest(void) return err; } +CHIP_ERROR SendWriteRequest(chip::app::WriteClient * apWriteClient) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + chip::TLV::TLVWriter * writer; + gLastMessageTime = chip::System::Timer::GetCurrentEpoch(); + chip::app::AttributePathParams attributePathParams; + + printf("\nSend write request message to Node: %" PRIu64 "\n", chip::kTestDeviceNodeId); + + attributePathParams.mNodeId = 1; + attributePathParams.mEndpointId = 2; + attributePathParams.mClusterId = 3; + attributePathParams.mFieldId = 4; + attributePathParams.mListIndex = 5; + attributePathParams.mFlags.Set(chip::app::AttributePathParams::Flags::kFieldIdValid); + + SuccessOrExit(err = apWriteClient->PrepareAttribute(attributePathParams)); + + writer = apWriteClient->GetAttributeDataElementTLVWriter(); + + SuccessOrExit(err = writer->PutBoolean(chip::TLV::ContextTag(chip::app::AttributeDataElement::kCsTag_Data), true)); + SuccessOrExit(err = apWriteClient->FinishAttribute()); + SuccessOrExit(err = apWriteClient->SendWriteRequest(chip::kTestDeviceNodeId, gAdminId, nullptr)); + + gWriteCount++; + +exit: + if (err != CHIP_NO_ERROR) + { + printf("Send read request failed, err: %s\n", chip::ErrorStr(err)); + } + return err; +} + CHIP_ERROR EstablishSecureSession() { CHIP_ERROR err = CHIP_NO_ERROR; @@ -237,9 +278,27 @@ void HandleReadComplete() gCond.notify_one(); } +void HandleWriteComplete() +{ + uint32_t respTime = chip::System::Timer::GetCurrentEpoch(); + uint32_t transitTime = respTime - gLastMessageTime; + + gWriteRespCount++; + + printf("Write Response: %" PRIu64 "/%" PRIu64 "(%.2f%%) time=%.3fms\n", gWriteRespCount, gWriteCount, + static_cast(gWriteRespCount) * 100 / gWriteCount, static_cast(transitTime) / 1000); + + gCond.notify_one(); +} + class MockInteractionModelApp : public chip::app::InteractionModelDelegate { public: + CHIP_ERROR WriteResponseProcessed(const chip::app::WriteClient * apWriteClient) override + { + HandleWriteComplete(); + return CHIP_NO_ERROR; + } CHIP_ERROR EventStreamReceived(const chip::Messaging::ExchangeContext * apExchangeContext, chip::TLV::TLVReader * apEventListReader) override { @@ -323,20 +382,6 @@ void DispatchSingleClusterCommand(chip::ClusterId aClusterId, chip::CommandId aC gLastCommandResult = TestCommandResult::kSuccess; } - -CHIP_ERROR WriteSingleClusterData(ClusterInfo & aClusterInfo, TLV::TLVReader & aReader) -{ - if (aClusterInfo.mClusterId != kTestClusterId || aClusterInfo.mEndpointId != kTestEndpointId) - { - return CHIP_ERROR_INVALID_ARGUMENT; - } - - if (aReader.GetLength() != 0) - { - chip::TLV::Debug::Dump(aReader, TLVPrettyPrinter); - } - return CHIP_NO_ERROR; -} } // namespace app } // namespace chip @@ -449,6 +494,26 @@ int main(int argc, char * argv[]) } } + // Connection has been established. Now send the ReadRequests. + for (unsigned int i = 0; i < kMaxWriteMessageCount; i++) + { + chip::app::WriteClient * writeClient; + err = chip::app::InteractionModelEngine::GetInstance()->NewWriteClient(&writeClient); + SuccessOrExit(err); + err = SendWriteRequest(writeClient); + + if (err != CHIP_NO_ERROR) + { + printf("Send write request failed: %s\n", chip::ErrorStr(err)); + goto exit; + } + + if (gCond.wait_for(lock, std::chrono::seconds(gMessageIntervalSeconds)) == std::cv_status::timeout) + { + printf("write request: No response received\n"); + } + } + gpReadClient->Shutdown(); chip::app::InteractionModelEngine::GetInstance()->Shutdown(); ShutdownChip(); @@ -465,6 +530,13 @@ int main(int argc, char * argv[]) printf("ChipReadClient failed: %s\n", chip::ErrorStr(err)); exit(EXIT_FAILURE); } + + if (err != CHIP_NO_ERROR || (gWriteRespCount != kMaxWriteMessageCount)) + { + printf("ChipWriteClient failed: %s\n", chip::ErrorStr(err)); + exit(EXIT_FAILURE); + } + printf("Test success \n"); return EXIT_SUCCESS; } diff --git a/src/app/tests/integration/chip_im_responder.cpp b/src/app/tests/integration/chip_im_responder.cpp index d05f75b9f82a7f..1c49d712c28ada 100644 --- a/src/app/tests/integration/chip_im_responder.cpp +++ b/src/app/tests/integration/chip_im_responder.cpp @@ -125,6 +125,23 @@ CHIP_ERROR ReadSingleClusterData(ClusterInfo & aClusterInfo, TLV::TLVWriter & aW ChipLogFunctError(err); return err; } + +CHIP_ERROR WriteSingleClusterData(ClusterInfo & aClusterInfo, TLV::TLVReader & aReader, WriteHandler * apWriteHandler) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + AttributePathParams attributePathParams; + attributePathParams.mNodeId = 1; + attributePathParams.mEndpointId = 2; + attributePathParams.mClusterId = 3; + attributePathParams.mFieldId = 4; + attributePathParams.mListIndex = 5; + attributePathParams.mFlags.Set(AttributePathParams::Flags::kFieldIdValid); + + err = apWriteHandler->AddAttributeStatusCode(attributePathParams, Protocols::SecureChannel::GeneralStatusCode::kSuccess, + Protocols::SecureChannel::Id, Protocols::InteractionModel::ProtocolCode::Success); + ChipLogFunctError(err); + return err; +} } // namespace app } // namespace chip