diff --git a/src/app/InteractionModelEngine.cpp b/src/app/InteractionModelEngine.cpp index 325ef7744aec74..7f821947b66c38 100644 --- a/src/app/InteractionModelEngine.cpp +++ b/src/app/InteractionModelEngine.cpp @@ -137,7 +137,7 @@ CHIP_ERROR InteractionModelEngine::NewCommandSender(CommandSender ** const apCom return CHIP_ERROR_NO_MEMORY; } -CHIP_ERROR InteractionModelEngine::NewReadClient(ReadClient ** const apReadClient, intptr_t aAppIdentifier) +CHIP_ERROR InteractionModelEngine::NewReadClient(ReadClient ** const apReadClient, uint64_t aAppIdentifier) { CHIP_ERROR err = CHIP_ERROR_NO_MEMORY; @@ -158,9 +158,9 @@ CHIP_ERROR InteractionModelEngine::NewReadClient(ReadClient ** const apReadClien return err; } -CHIP_ERROR InteractionModelEngine::NewWriteClient(WriteClient ** const apWriteClient) +CHIP_ERROR InteractionModelEngine::NewWriteClient(WriteClientHandle & apWriteClient, uint64_t aApplicationIdentifier) { - *apWriteClient = nullptr; + apWriteClient.SetWriteClient(nullptr); for (auto & writeClient : mWriteClients) { @@ -169,8 +169,8 @@ CHIP_ERROR InteractionModelEngine::NewWriteClient(WriteClient ** const apWriteCl continue; } - ReturnErrorOnFailure(writeClient.Init(mpExchangeMgr, mpDelegate)); - *apWriteClient = &writeClient; + ReturnLogErrorOnFailure(writeClient.Init(mpExchangeMgr, mpDelegate, aApplicationIdentifier)); + apWriteClient.SetWriteClient(&writeClient); return CHIP_NO_ERROR; } @@ -321,7 +321,7 @@ CHIP_ERROR InteractionModelEngine::SendReadRequest(NodeId aNodeId, FabricIndex a EventPathParams * apEventPathParamsList, size_t aEventPathParamsListSize, AttributePathParams * apAttributePathParamsList, size_t aAttributePathParamsListSize, EventNumber aEventNumber, - intptr_t aAppIdentifier) + uint64_t aAppIdentifier) { ReadClient * client = nullptr; CHIP_ERROR err = CHIP_NO_ERROR; @@ -335,19 +335,6 @@ CHIP_ERROR InteractionModelEngine::SendReadRequest(NodeId aNodeId, FabricIndex a return err; } -CHIP_ERROR __attribute__((weak)) -WriteSingleClusterData(ClusterInfo & aClusterInfo, TLV::TLVReader & aReader, WriteHandler * apWriteHandler) -{ - ChipLogDetail(DataManagement, - "Received Cluster Attribute: Cluster=" ChipLogFormatMEI " NodeId=0x" ChipLogFormatX64 " Endpoint=%" PRIx16 - " FieldId=%" PRIx32 " ListIndex=%" PRIx16, - ChipLogValueMEI(aClusterInfo.mClusterId), ChipLogValueX64(aClusterInfo.mNodeId), aClusterInfo.mEndpointId, - aClusterInfo.mFieldId, aClusterInfo.mListIndex); - ChipLogError(DataManagement, - "Default WriteSingleClusterData is called, this should be replaced by actual dispatched for cluster"); - return CHIP_NO_ERROR; -} - uint16_t InteractionModelEngine::GetReadClientArrayIndex(const ReadClient * const apReadClient) const { return static_cast(apReadClient - mReadClients); diff --git a/src/app/InteractionModelEngine.h b/src/app/InteractionModelEngine.h index eb6d3cf3a44c8a..c3bd403361f80a 100644 --- a/src/app/InteractionModelEngine.h +++ b/src/app/InteractionModelEngine.h @@ -115,18 +115,22 @@ class InteractionModelEngine : public Messaging::ExchangeDelegate CHIP_ERROR SendReadRequest(NodeId aNodeId, FabricIndex aFabricIndex, SecureSessionHandle * apSecureSession, EventPathParams * apEventPathParamsList, size_t aEventPathParamsListSize, AttributePathParams * apAttributePathParamsList, size_t aAttributePathParamsListSize, - EventNumber aEventNumber, intptr_t aAppIdentifier = 0); + EventNumber aEventNumber, uint64_t aAppIdentifier = 0); /** * Retrieve a WriteClient that the SDK consumer can use to send a write. If the call succeeds, * see WriteClient documentation for lifetime handling. * + * The Write interaction is more like Invoke interaction (cluster specific commands) since it will include cluster specific + * payload, and may have the need to encode non-scalar values (like structs and arrays). Thus we use WriteClientHandle to + * prevent user's code from leaking WriteClients. + * * @param[out] apWriteClient A pointer to the WriteClient object. * * @retval #CHIP_ERROR_NO_MEMORY If there is no WriteClient available * @retval #CHIP_NO_ERROR On success. */ - CHIP_ERROR NewWriteClient(WriteClient ** const apWriteClient); + CHIP_ERROR NewWriteClient(WriteClientHandle & apWriteClient, uint64_t aApplicationIdentifier = 0); /** * Get read client index in mReadClients @@ -177,7 +181,7 @@ class InteractionModelEngine : public Messaging::ExchangeDelegate * @retval #CHIP_ERROR_INCORRECT_STATE If there is no ReadClient available * @retval #CHIP_NO_ERROR On success. */ - CHIP_ERROR NewReadClient(ReadClient ** const apReadClient, intptr_t aAppIdentifier); + CHIP_ERROR NewReadClient(ReadClient ** const apReadClient, uint64_t aAppIdentifier); Messaging::ExchangeManager * mpExchangeMgr = nullptr; InteractionModelDelegate * mpDelegate = nullptr; diff --git a/src/app/ReadClient.cpp b/src/app/ReadClient.cpp index 65d505cc7c7cad..070e754c3b9eed 100644 --- a/src/app/ReadClient.cpp +++ b/src/app/ReadClient.cpp @@ -30,7 +30,7 @@ namespace chip { namespace app { CHIP_ERROR ReadClient::Init(Messaging::ExchangeManager * apExchangeMgr, InteractionModelDelegate * apDelegate, - intptr_t aAppIdentifier) + uint64_t aAppIdentifier) { CHIP_ERROR err = CHIP_NO_ERROR; // Error if already initialized. diff --git a/src/app/ReadClient.h b/src/app/ReadClient.h index 211f9dbd170f4b..a446a6bef60f3a 100644 --- a/src/app/ReadClient.h +++ b/src/app/ReadClient.h @@ -77,7 +77,7 @@ class ReadClient : public Messaging::ExchangeDelegate AttributePathParams * apAttributePathParamsList, size_t aAttributePathParamsListSize, EventNumber aEventNumber); - intptr_t GetAppIdentifier() const { return mAppIdentifier; } + uint64_t GetAppIdentifier() const { return mAppIdentifier; } Messaging::ExchangeContext * GetExchangeContext() const { return mpExchangeCtx; } private: @@ -105,7 +105,7 @@ class ReadClient : public Messaging::ExchangeDelegate * @retval #CHIP_NO_ERROR On success. * */ - CHIP_ERROR Init(Messaging::ExchangeManager * apExchangeMgr, InteractionModelDelegate * apDelegate, intptr_t aAppIdentifier); + CHIP_ERROR Init(Messaging::ExchangeManager * apExchangeMgr, InteractionModelDelegate * apDelegate, uint64_t aAppIdentifier); virtual ~ReadClient() = default; @@ -140,7 +140,7 @@ class ReadClient : public Messaging::ExchangeDelegate Messaging::ExchangeContext * mpExchangeCtx = nullptr; InteractionModelDelegate * mpDelegate = nullptr; ClientState mState = ClientState::Uninitialized; - intptr_t mAppIdentifier = 0; + uint64_t mAppIdentifier = 0; }; }; // namespace app diff --git a/src/app/WriteClient.cpp b/src/app/WriteClient.cpp index f69794cf1f811b..0e5d07ca5722ec 100644 --- a/src/app/WriteClient.cpp +++ b/src/app/WriteClient.cpp @@ -29,7 +29,8 @@ namespace chip { namespace app { -CHIP_ERROR WriteClient::Init(Messaging::ExchangeManager * apExchangeMgr, InteractionModelDelegate * apDelegate) +CHIP_ERROR WriteClient::Init(Messaging::ExchangeManager * apExchangeMgr, InteractionModelDelegate * apDelegate, + uint64_t aApplicationIdentifier) { VerifyOrReturnError(apExchangeMgr != nullptr, CHIP_ERROR_INVALID_ARGUMENT); VerifyOrReturnError(mpExchangeMgr == nullptr, CHIP_ERROR_INCORRECT_STATE); @@ -50,6 +51,7 @@ CHIP_ERROR WriteClient::Init(Messaging::ExchangeManager * apExchangeMgr, Interac mpExchangeMgr = apExchangeMgr; mpDelegate = apDelegate; mAttributeStatusIndex = 0; + mAppIdentifier = aApplicationIdentifier; MoveToState(State::Initialized); return CHIP_NO_ERROR; @@ -396,5 +398,21 @@ CHIP_ERROR WriteClient::ProcessAttributeStatusElement(AttributeStatusElement::Pa return err; } +CHIP_ERROR WriteClientHandle::SendWriteRequest(NodeId aNodeId, FabricIndex aFabricIndex, SecureSessionHandle * apSecureSession) +{ + CHIP_ERROR err = mpWriteClient->SendWriteRequest(aNodeId, aFabricIndex, apSecureSession); + + if (err == CHIP_NO_ERROR) + { + // On success, the write client will handle lifetime up until success/falure callbacks are invoked + mpWriteClient = nullptr; + } + else + { + SetWriteClient(nullptr); + } + return err; +} + } // namespace app } // namespace chip diff --git a/src/app/WriteClient.h b/src/app/WriteClient.h index 91f953cc58f5bd..63f939fd290802 100644 --- a/src/app/WriteClient.h +++ b/src/app/WriteClient.h @@ -36,6 +36,10 @@ namespace chip { namespace app { + +class WriteClientHandle; +class InteractionModelEngine; + /** * @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. @@ -52,22 +56,21 @@ class WriteClient : public Messaging::ExchangeDelegate */ void Shutdown(); - /** - * Once SendWriteRequest returns successfully, the WriteClient will - * handle calling Shutdown on itself once it decides it's done with waiting - * for a response (i.e. times out or gets a response). - * If SendWriteRequest is never called, or the call fails, the API - * consumer is responsible for calling Shutdown on the WriteClient. - */ - CHIP_ERROR SendWriteRequest(NodeId aNodeId, FabricIndex aFabricIndex, SecureSessionHandle * apSecureSession); - CHIP_ERROR PrepareAttribute(const AttributePathParams & attributePathParams); CHIP_ERROR FinishAttribute(); TLV::TLVWriter * GetAttributeDataElementTLVWriter(); + uint64_t GetAppIdentifier() const { return mAppIdentifier; } + void SetAppIdentifier(uint64_t aAppIdentifier) { mAppIdentifier = aAppIdentifier; } + NodeId GetSourceNodeId() const + { + return mpExchangeCtx != nullptr ? mpExchangeCtx->GetSecureSession().GetPeerNodeId() : kUndefinedNodeId; + } + private: friend class TestWriteInteraction; friend class InteractionModelEngine; + friend class WriteClientHandle; enum class State { @@ -77,6 +80,20 @@ class WriteClient : public Messaging::ExchangeDelegate AwaitingResponse, // The client has sent out the write request message }; + /** + * Finalize Write Request Message TLV Builder and retrieve final data from tlv builder for later sending + */ + CHIP_ERROR FinalizeMessage(System::PacketBufferHandle & aPacket); + + /** + * Once SendWriteRequest returns successfully, the WriteClient will + * handle calling Shutdown on itself once it decides it's done with waiting + * for a response (i.e. times out or gets a response). + * If SendWriteRequest is never called, or the call fails, the API + * consumer is responsible for calling Shutdown on the WriteClient. + */ + CHIP_ERROR SendWriteRequest(NodeId aNodeId, FabricIndex aFabricIndex, SecureSessionHandle * apSecureSession); + /** * Initialize the client object. Within the lifetime * of this instance, this method is invoked once after object @@ -88,7 +105,8 @@ class WriteClient : public Messaging::ExchangeDelegate * @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); + CHIP_ERROR Init(Messaging::ExchangeManager * apExchangeMgr, InteractionModelDelegate * apDelegate, + uint64_t aApplicationIdentifier); virtual ~WriteClient() = default; @@ -101,11 +119,6 @@ class WriteClient : public Messaging::ExchangeDelegate */ bool IsFree() const { return mState == State::Uninitialized; }; - /** - * Finalize Write Request Message TLV Builder and retrieve final data from tlv builder for later sending - */ - CHIP_ERROR FinalizeMessage(System::PacketBufferHandle & aPacket); - void MoveToState(const State aTargetState); CHIP_ERROR ProcessWriteResponseMessage(System::PacketBufferHandle && payload); CHIP_ERROR ProcessAttributeStatusElement(AttributeStatusElement::Parser & aAttributeStatusElement); @@ -128,7 +141,75 @@ class WriteClient : public Messaging::ExchangeDelegate System::PacketBufferTLVWriter mMessageWriter; WriteRequest::Builder mWriteRequestBuilder; uint8_t mAttributeStatusIndex = 0; - intptr_t mAppIdentifier = 0; + uint64_t mAppIdentifier = 0; +}; + +class WriteClientHandle +{ +public: + /** + * Construct an empty WriteClientHandle. + */ + WriteClientHandle() : mpWriteClient(nullptr) {} + WriteClientHandle(decltype(nullptr)) : mpWriteClient(nullptr) {} + + /** + * Construct a WriteClientHandle that takes ownership of a WriteClient from another. + */ + WriteClientHandle(WriteClientHandle && aOther) + { + mpWriteClient = aOther.mpWriteClient; + aOther.mpWriteClient = nullptr; + } + + ~WriteClientHandle() { SetWriteClient(nullptr); } + + /** + * Access a WriteClientHandle's public methods. + */ + WriteClient * operator->() const { return mpWriteClient; } + + /** + * Finalize the message and send it to the desired node. The underlying write object will always be released, and the user + * should not use this object after calling this function. + */ + CHIP_ERROR SendWriteRequest(NodeId aNodeId, FabricIndex aFabricIndex, SecureSessionHandle * apSecureSession); + + /** + * Encode an attribute value that can be directly encoded using TLVWriter::Put + */ + template + CHIP_ERROR EncodeScalarAttributeWritePayload(const chip::app::AttributePathParams & attributePath, T value) + { + chip::TLV::TLVWriter * writer = nullptr; + + VerifyOrReturnError(mpWriteClient != nullptr, CHIP_ERROR_INCORRECT_STATE); + ReturnErrorOnFailure(app::InteractionModelEngine::GetInstance()->NewWriteClient(handle)); + ReturnErrorOnFailure(mpWriteClient->PrepareAttribute(attributePath)); + VerifyOrReturnError((writer = mpWriteClient->GetAttributeDataElementTLVWriter()) != nullptr, CHIP_ERROR_INCORRECT_STATE); + ReturnErrorOnFailure(mpWriteClient->Put(chip::TLV::ContextTag(chip::app::AttributeDataElement::kCsTag_Data), value)); + ReturnErrorOnFailure(mpWriteClient->FinishAttribute()); + + return CHIP_NO_ERROR; + } + + void SetWriteClient(WriteClient * apWriteClient) + { + if (mpWriteClient != nullptr) + { + mpWriteClient->Shutdown(); + } + mpWriteClient = apWriteClient; + } + +private: + friend class TestWriteInteraction; + + WriteClientHandle(const WriteClientHandle &) = delete; + WriteClientHandle & operator=(const WriteClientHandle &) = delete; + WriteClientHandle & operator=(const WriteClientHandle &&) = delete; + + WriteClient * mpWriteClient = nullptr; }; } // namespace app diff --git a/src/app/WriteHandler.cpp b/src/app/WriteHandler.cpp index 672bab8d207db8..87553d001922cf 100644 --- a/src/app/WriteHandler.cpp +++ b/src/app/WriteHandler.cpp @@ -27,17 +27,17 @@ namespace chip { namespace app { CHIP_ERROR WriteHandler::Init(InteractionModelDelegate * apDelegate) { - VerifyOrReturnError(apDelegate != nullptr, CHIP_ERROR_INVALID_ARGUMENT); - VerifyOrReturnError(mpExchangeCtx == nullptr, CHIP_ERROR_INCORRECT_STATE); + IgnoreUnusedVariable(apDelegate); + VerifyOrReturnLogError(mpExchangeCtx == nullptr, CHIP_ERROR_INCORRECT_STATE); System::PacketBufferHandle packet = System::PacketBufferHandle::New(chip::app::kMaxSecureSduLengthBytes); - VerifyOrReturnError(!packet.IsNull(), CHIP_ERROR_NO_MEMORY); + VerifyOrReturnLogError(!packet.IsNull(), CHIP_ERROR_NO_MEMORY); mMessageWriter.Init(std::move(packet)); - ReturnErrorOnFailure(mWriteResponseBuilder.Init(&mMessageWriter)); + ReturnLogErrorOnFailure(mWriteResponseBuilder.Init(&mMessageWriter)); AttributeStatusList::Builder attributeStatusListBuilder = mWriteResponseBuilder.CreateAttributeStatusListBuilder(); - ReturnErrorOnFailure(attributeStatusListBuilder.GetError()); + ReturnLogErrorOnFailure(attributeStatusListBuilder.GetError()); MoveToState(State::Initialized); diff --git a/src/app/tests/TestWriteInteraction.cpp b/src/app/tests/TestWriteInteraction.cpp index 20ad82fc2b995e..2ff0a705e06259 100644 --- a/src/app/tests/TestWriteInteraction.cpp +++ b/src/app/tests/TestWriteInteraction.cpp @@ -56,7 +56,7 @@ class TestWriteInteraction static void TestWriteRoundtrip(nlTestSuite * apSuite, void * apContext); private: - static void AddAttributeDataElement(nlTestSuite * apSuite, void * apContext, WriteClient & aWriteClient); + static void AddAttributeDataElement(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 GenerateWriteResponse(nlTestSuite * apSuite, void * apContext, System::PacketBufferHandle & aPayload); @@ -73,7 +73,7 @@ class TestExchangeDelegate : public Messaging::ExchangeDelegate void OnResponseTimeout(Messaging::ExchangeContext * ec) override {} }; -void TestWriteInteraction::AddAttributeDataElement(nlTestSuite * apSuite, void * apContext, WriteClient & aWriteClient) +void TestWriteInteraction::AddAttributeDataElement(nlTestSuite * apSuite, void * apContext, WriteClientHandle & aWriteClient) { CHIP_ERROR err = CHIP_NO_ERROR; AttributePathParams attributePathParams; @@ -84,15 +84,15 @@ void TestWriteInteraction::AddAttributeDataElement(nlTestSuite * apSuite, void * attributePathParams.mListIndex = 5; attributePathParams.mFlags.Set(AttributePathParams::Flags::kFieldIdValid); - err = aWriteClient.PrepareAttribute(attributePathParams); + err = aWriteClient->PrepareAttribute(attributePathParams); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); - chip::TLV::TLVWriter * writer = aWriteClient.GetAttributeDataElementTLVWriter(); + chip::TLV::TLVWriter * writer = aWriteClient->GetAttributeDataElementTLVWriter(); err = writer->PutBoolean(chip::TLV::ContextTag(chip::app::AttributeDataElement::kCsTag_Data), true); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); - err = aWriteClient.FinishAttribute(); + err = aWriteClient->FinishAttribute(); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); } @@ -206,16 +206,20 @@ void TestWriteInteraction::TestWriteClient(nlTestSuite * apSuite, void * apConte CHIP_ERROR err = CHIP_NO_ERROR; app::WriteClient writeClient; + app::WriteClientHandle writeClientHandle; + writeClientHandle.SetWriteClient(&writeClient); chip::app::InteractionModelDelegate delegate; System::PacketBufferHandle buf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize); - err = writeClient.Init(&ctx.GetExchangeManager(), &delegate); + err = writeClient.Init(&ctx.GetExchangeManager(), &delegate, 0); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); - AddAttributeDataElement(apSuite, apContext, writeClient); + AddAttributeDataElement(apSuite, apContext, writeClientHandle); SecureSessionHandle session = ctx.GetSessionLocalToPeer(); - err = writeClient.SendWriteRequest(ctx.GetDestinationNodeId(), ctx.GetFabricIndex(), &session); + err = writeClientHandle.SendWriteRequest(ctx.GetDestinationNodeId(), ctx.GetFabricIndex(), &session); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + // The internal WriteClient should be nullptr once we SendWriteRequest. + NL_TEST_ASSERT(apSuite, nullptr == writeClientHandle.mpWriteClient); GenerateWriteResponse(apSuite, apContext, buf); @@ -290,17 +294,18 @@ void TestWriteInteraction::TestWriteRoundtrip(nlTestSuite * apSuite, void * apCo err = engine->Init(&ctx.GetExchangeManager(), &delegate); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); - app::WriteClient * writeClient; - err = engine->NewWriteClient(&writeClient); + app::WriteClientHandle writeClient; + err = engine->NewWriteClient(writeClient); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); System::PacketBufferHandle buf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize); - AddAttributeDataElement(apSuite, apContext, *writeClient); + AddAttributeDataElement(apSuite, apContext, writeClient); NL_TEST_ASSERT(apSuite, !delegate.mGotResponse); SecureSessionHandle session = ctx.GetSessionLocalToPeer(); - err = writeClient->SendWriteRequest(ctx.GetDestinationNodeId(), ctx.GetFabricIndex(), &session); + + err = writeClient.SendWriteRequest(ctx.GetDestinationNodeId(), ctx.GetFabricIndex(), &session); NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); NL_TEST_ASSERT(apSuite, delegate.mGotResponse); diff --git a/src/app/tests/integration/chip_im_initiator.cpp b/src/app/tests/integration/chip_im_initiator.cpp index 1f51e7adc6ecb2..72cff98a5bd0b4 100644 --- a/src/app/tests/integration/chip_im_initiator.cpp +++ b/src/app/tests/integration/chip_im_initiator.cpp @@ -213,7 +213,7 @@ CHIP_ERROR SendReadRequest() return err; } -CHIP_ERROR SendWriteRequest(chip::app::WriteClient * apWriteClient) +CHIP_ERROR SendWriteRequest(chip::app::WriteClientHandle & apWriteClient) { CHIP_ERROR err = CHIP_NO_ERROR; chip::TLV::TLVWriter * writer; @@ -235,7 +235,7 @@ CHIP_ERROR SendWriteRequest(chip::app::WriteClient * apWriteClient) SuccessOrExit(err = writer->PutBoolean(chip::TLV::ContextTag(chip::app::AttributeDataElement::kCsTag_Data), true)); SuccessOrExit(err = apWriteClient->FinishAttribute()); - SuccessOrExit(err = apWriteClient->SendWriteRequest(chip::kTestDeviceNodeId, gFabricIndex, nullptr)); + SuccessOrExit(err = apWriteClient.SendWriteRequest(chip::kTestDeviceNodeId, gFabricIndex, nullptr)); gWriteCount++; @@ -400,8 +400,8 @@ void WriteRequestTimerHandler(chip::System::Layer * systemLayer, void * appState if (gWriteRespCount < kMaxWriteMessageCount) { - chip::app::WriteClient * writeClient; - err = chip::app::InteractionModelEngine::GetInstance()->NewWriteClient(&writeClient); + chip::app::WriteClientHandle writeClient; + err = chip::app::InteractionModelEngine::GetInstance()->NewWriteClient(writeClient); SuccessOrExit(err); err = SendWriteRequest(writeClient); @@ -528,7 +528,7 @@ CHIP_ERROR ReadSingleClusterData(ClusterInfo & aClusterInfo, TLV::TLVWriter * ap chip::to_underlying(Protocols::InteractionModel::ProtocolCode::UnsupportedAttribute)); } -CHIP_ERROR WriteSingleClusterData(ClusterInfo & aClusterInfo, TLV::TLVReader & aReader) +CHIP_ERROR WriteSingleClusterData(ClusterInfo & aClusterInfo, TLV::TLVReader & aReader, WriteHandler *) { if (aClusterInfo.mClusterId != kTestClusterId || aClusterInfo.mEndpointId != kTestEndpointId) { diff --git a/src/app/util/ember-compatibility-functions.cpp b/src/app/util/ember-compatibility-functions.cpp index 0e3ffcdddb1974..b62fca0d4ff3fa 100644 --- a/src/app/util/ember-compatibility-functions.cpp +++ b/src/app/util/ember-compatibility-functions.cpp @@ -25,12 +25,14 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include #include @@ -177,6 +179,11 @@ void ResetEmberAfObjects() } // namespace Compatibility +namespace { +// Common buffer for ReadSingleClusterData & WriteSingleClusterData +uint8_t attributeData[kAttributeReadBufferSize]; +} // namespace + bool ServerClusterCommandExists(chip::ClusterId aClusterId, chip::CommandId aCommandId, chip::EndpointId aEndPointId) { // TODO: Currently, we are using cluster catalog from the ember library, this should be modified or replaced after several @@ -186,8 +193,6 @@ bool ServerClusterCommandExists(chip::ClusterId aClusterId, chip::CommandId aCom CHIP_ERROR ReadSingleClusterData(ClusterInfo & aClusterInfo, TLV::TLVWriter * apWriter, bool * apDataExists) { - static uint8_t data[kAttributeReadBufferSize]; - ChipLogDetail(DataManagement, "Received Cluster Command: Cluster=" ChipLogFormatMEI " NodeId=0x" ChipLogFormatX64 " Endpoint=%" PRIx16 " FieldId=%" PRIx32 " ListIndex=%" PRIx16, @@ -197,7 +202,7 @@ CHIP_ERROR ReadSingleClusterData(ClusterInfo & aClusterInfo, TLV::TLVWriter * ap EmberAfAttributeType attributeType; EmberAfStatus status; status = emberAfReadAttribute(aClusterInfo.mEndpointId, aClusterInfo.mClusterId, aClusterInfo.mFieldId, CLUSTER_MASK_SERVER, - data, sizeof(data), &attributeType); + attributeData, sizeof(attributeData), &attributeType); if (apDataExists != nullptr) { @@ -218,64 +223,64 @@ CHIP_ERROR ReadSingleClusterData(ClusterInfo & aClusterInfo, TLV::TLVWriter * ap ReturnErrorOnFailure(apWriter->PutNull(TLV::ContextTag(AttributeDataElement::kCsTag_Data))); break; case ZCL_BOOLEAN_ATTRIBUTE_TYPE: // Boolean - ReturnErrorOnFailure(apWriter->PutBoolean(TLV::ContextTag(AttributeDataElement::kCsTag_Data), !!data[0])); + ReturnErrorOnFailure(apWriter->PutBoolean(TLV::ContextTag(AttributeDataElement::kCsTag_Data), !!attributeData[0])); break; case ZCL_INT8U_ATTRIBUTE_TYPE: // Unsigned 8-bit integer - ReturnErrorOnFailure(apWriter->Put(TLV::ContextTag(AttributeDataElement::kCsTag_Data), data[0])); + ReturnErrorOnFailure(apWriter->Put(TLV::ContextTag(AttributeDataElement::kCsTag_Data), attributeData[0])); break; case ZCL_INT16U_ATTRIBUTE_TYPE: // Unsigned 16-bit integer { uint16_t uint16_data; - memcpy(&uint16_data, data, sizeof(uint16_data)); + memcpy(&uint16_data, attributeData, sizeof(uint16_data)); ReturnErrorOnFailure(apWriter->Put(TLV::ContextTag(AttributeDataElement::kCsTag_Data), uint16_data)); break; } case ZCL_INT32U_ATTRIBUTE_TYPE: // Unsigned 32-bit integer { uint32_t uint32_data; - memcpy(&uint32_data, data, sizeof(uint32_data)); + memcpy(&uint32_data, attributeData, sizeof(uint32_data)); ReturnErrorOnFailure(apWriter->Put(TLV::ContextTag(AttributeDataElement::kCsTag_Data), uint32_data)); break; } case ZCL_INT64U_ATTRIBUTE_TYPE: // Unsigned 64-bit integer { uint64_t uint64_data; - memcpy(&uint64_data, data, sizeof(uint64_data)); + memcpy(&uint64_data, attributeData, sizeof(uint64_data)); ReturnErrorOnFailure(apWriter->Put(TLV::ContextTag(AttributeDataElement::kCsTag_Data), uint64_data)); break; } case ZCL_INT8S_ATTRIBUTE_TYPE: // Signed 8-bit integer { int8_t int8_data; - memcpy(&int8_data, data, sizeof(int8_data)); + memcpy(&int8_data, attributeData, sizeof(int8_data)); ReturnErrorOnFailure(apWriter->Put(TLV::ContextTag(AttributeDataElement::kCsTag_Data), int8_data)); break; } case ZCL_INT16S_ATTRIBUTE_TYPE: // Signed 16-bit integer { int16_t int16_data; - memcpy(&int16_data, data, sizeof(int16_data)); + memcpy(&int16_data, attributeData, sizeof(int16_data)); ReturnErrorOnFailure(apWriter->Put(TLV::ContextTag(AttributeDataElement::kCsTag_Data), int16_data)); break; } case ZCL_INT32S_ATTRIBUTE_TYPE: // Signed 32-bit integer { int32_t int32_data; - memcpy(&int32_data, data, sizeof(int32_data)); + memcpy(&int32_data, attributeData, sizeof(int32_data)); ReturnErrorOnFailure(apWriter->Put(TLV::ContextTag(AttributeDataElement::kCsTag_Data), int32_data)); break; } case ZCL_INT64S_ATTRIBUTE_TYPE: // Signed 64-bit integer { int64_t int64_data; - memcpy(&int64_data, data, sizeof(int64_data)); + memcpy(&int64_data, attributeData, sizeof(int64_data)); ReturnErrorOnFailure(apWriter->Put(TLV::ContextTag(AttributeDataElement::kCsTag_Data), int64_data)); break; } case ZCL_CHAR_STRING_ATTRIBUTE_TYPE: // Char string { - char * actualData = reinterpret_cast(data + 1); - uint8_t dataLength = data[0]; + char * actualData = reinterpret_cast(attributeData + 1); + uint8_t dataLength = attributeData[0]; if (dataLength == 0xFF /* invalid data, put empty value instead */) { dataLength = 0; @@ -284,9 +289,9 @@ CHIP_ERROR ReadSingleClusterData(ClusterInfo & aClusterInfo, TLV::TLVWriter * ap break; } case ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE: { - char * actualData = reinterpret_cast(data + 2); // The pascal string contains 2 bytes length + char * actualData = reinterpret_cast(attributeData + 2); // The pascal string contains 2 bytes length uint16_t dataLength; - memcpy(&dataLength, data, sizeof(dataLength)); + memcpy(&dataLength, attributeData, sizeof(dataLength)); if (dataLength == 0xFFFF /* invalid data, put empty value instead */) { dataLength = 0; @@ -296,8 +301,8 @@ CHIP_ERROR ReadSingleClusterData(ClusterInfo & aClusterInfo, TLV::TLVWriter * ap } case ZCL_OCTET_STRING_ATTRIBUTE_TYPE: // Octet string { - uint8_t * actualData = data + 1; - uint8_t dataLength = data[0]; + uint8_t * actualData = attributeData + 1; + uint8_t dataLength = attributeData[0]; if (dataLength == 0xFF /* invalid data, put empty value instead */) { dataLength = 0; @@ -307,9 +312,9 @@ CHIP_ERROR ReadSingleClusterData(ClusterInfo & aClusterInfo, TLV::TLVWriter * ap break; } case ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE: { - uint8_t * actualData = data + 2; // The pascal string contains 2 bytes length + uint8_t * actualData = attributeData + 2; // The pascal string contains 2 bytes length uint16_t dataLength; - memcpy(&dataLength, data, sizeof(dataLength)); + memcpy(&dataLength, attributeData, sizeof(dataLength)); if (dataLength == 0xFFFF /* invalid data, put empty value instead */) { dataLength = 0; @@ -323,9 +328,9 @@ CHIP_ERROR ReadSingleClusterData(ClusterInfo & aClusterInfo, TLV::TLVWriter * ap ReturnErrorOnFailure( apWriter->StartContainer(TLV::ContextTag(AttributeDataElement::kCsTag_Data), TLV::kTLVType_List, containerType)); // TODO: Encode data in TLV, now raw buffers - ReturnErrorOnFailure( - apWriter->PutBytes(TLV::AnonymousTag, data, - emberAfAttributeValueSize(aClusterInfo.mClusterId, aClusterInfo.mFieldId, attributeType, data))); + ReturnErrorOnFailure(apWriter->PutBytes( + TLV::AnonymousTag, attributeData, + emberAfAttributeValueSize(aClusterInfo.mClusterId, aClusterInfo.mFieldId, attributeType, attributeData))); ReturnErrorOnFailure(apWriter->EndContainer(containerType)); break; } @@ -340,5 +345,117 @@ CHIP_ERROR ReadSingleClusterData(ClusterInfo & aClusterInfo, TLV::TLVWriter * ap return CHIP_NO_ERROR; } +namespace { +template +CHIP_ERROR numericTlvDataToAttributeBuffer(TLV::TLVReader & aReader, uint16_t & dataLen) +{ + T value; + static_assert(sizeof(value) <= sizeof(attributeData), "Value cannot fit into attribute data"); + ReturnErrorOnFailure(aReader.Get(value)); + dataLen = sizeof(value); + memcpy(attributeData, &value, sizeof(value)); + return CHIP_NO_ERROR; +} +template +CHIP_ERROR stringTlvDataToAttributeBuffer(TLV::TLVReader & aReader, uint16_t & dataLen) +{ + const uint8_t * data = nullptr; + T len; + VerifyOrReturnError(aReader.GetType() == TLV::TLVType::kTLVType_ByteString || + aReader.GetType() == TLV::TLVType::kTLVType_UTF8String, + CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(CanCastTo(aReader.GetLength()), CHIP_ERROR_MESSAGE_TOO_LONG); + ReturnErrorOnFailure(aReader.GetDataPtr(data)); + len = static_cast(aReader.GetLength()); + VerifyOrReturnError(len + sizeof(len) /* length at the beginning of data */ <= sizeof(attributeData), + CHIP_ERROR_MESSAGE_TOO_LONG); + memcpy(&attributeData[0], &len, sizeof(len)); + memcpy(&attributeData[sizeof(len)], data, len); + dataLen = static_cast(len + sizeof(len)); + return CHIP_NO_ERROR; +} + +CHIP_ERROR prepareWriteData(EmberAfAttributeType expectedType, TLV::TLVReader & aReader, uint16_t & dataLen) +{ + switch (BaseType(expectedType)) + { + case ZCL_BOOLEAN_ATTRIBUTE_TYPE: // Boolean + return numericTlvDataToAttributeBuffer(aReader, dataLen); + case ZCL_INT8U_ATTRIBUTE_TYPE: // Unsigned 8-bit integer + return numericTlvDataToAttributeBuffer(aReader, dataLen); + case ZCL_INT16U_ATTRIBUTE_TYPE: // Unsigned 16-bit integer + return numericTlvDataToAttributeBuffer(aReader, dataLen); + case ZCL_INT32U_ATTRIBUTE_TYPE: // Unsigned 32-bit integer + return numericTlvDataToAttributeBuffer(aReader, dataLen); + case ZCL_INT64U_ATTRIBUTE_TYPE: // Unsigned 64-bit integer + return numericTlvDataToAttributeBuffer(aReader, dataLen); + case ZCL_INT8S_ATTRIBUTE_TYPE: // Signed 8-bit integer + return numericTlvDataToAttributeBuffer(aReader, dataLen); + case ZCL_INT16S_ATTRIBUTE_TYPE: // Signed 16-bit integer + return numericTlvDataToAttributeBuffer(aReader, dataLen); + case ZCL_INT32S_ATTRIBUTE_TYPE: // Signed 32-bit integer + return numericTlvDataToAttributeBuffer(aReader, dataLen); + case ZCL_INT64S_ATTRIBUTE_TYPE: // Signed 64-bit integer + return numericTlvDataToAttributeBuffer(aReader, dataLen); + case ZCL_OCTET_STRING_ATTRIBUTE_TYPE: // Octet string + case ZCL_CHAR_STRING_ATTRIBUTE_TYPE: // Char string + return stringTlvDataToAttributeBuffer(aReader, dataLen); + case ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE: // Long octet string + case ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE: // Long char string + return stringTlvDataToAttributeBuffer(aReader, dataLen); + default: + ChipLogError(DataManagement, "Attribute type %x not handled", static_cast(expectedType)); + return CHIP_ERROR_INVALID_DATA_LIST; + } +} +} // namespace + +CHIP_ERROR WriteSingleClusterData(ClusterInfo & aClusterInfo, TLV::TLVReader & aReader, WriteHandler * apWriteHandler) +{ + AttributePathParams attributePathParams; + attributePathParams.mNodeId = aClusterInfo.mNodeId; + attributePathParams.mEndpointId = aClusterInfo.mEndpointId; + attributePathParams.mClusterId = aClusterInfo.mClusterId; + attributePathParams.mFieldId = aClusterInfo.mFieldId; + attributePathParams.mFlags.Set(AttributePathParams::Flags::kFieldIdValid); + + EmberAfAttributeType attributeType = ZCL_UNKNOWN_ATTRIBUTE_TYPE; + + // Passing nullptr as buf to emberAfReadAttribute means we only need attribute type here, and ember will not do data read & + // copy in this case. + EmberAfStatus status = emberAfReadAttribute(aClusterInfo.mEndpointId, aClusterInfo.mClusterId, aClusterInfo.mFieldId, + CLUSTER_MASK_SERVER, nullptr, 0, &attributeType); + Protocols::InteractionModel::ProtocolCode imCode = Protocols::InteractionModel::ProtocolCode::Success; + + if (EMBER_ZCL_STATUS_SUCCESS != status) + { + return apWriteHandler->AddAttributeStatusCode(attributePathParams, Protocols::SecureChannel::GeneralStatusCode::kFailure, + Protocols::SecureChannel::Id, + Protocols::InteractionModel::ProtocolCode::UnsupportedAttribute); + } + + CHIP_ERROR preparationError = CHIP_NO_ERROR; + uint16_t dataLen = 0; + if ((preparationError = prepareWriteData(attributeType, aReader, dataLen)) == CHIP_NO_ERROR) + { + // TODO (#8442): emberAfWriteAttributeExternal is doing additional ACL check, however true ACL support is missing in ember / + // IM. Should invesgate this function and integrate ACL support with related interactions. + imCode = ToInteractionModelProtocolCode(emberAfWriteAttributeExternal(aClusterInfo.mEndpointId, aClusterInfo.mClusterId, + aClusterInfo.mFieldId, CLUSTER_MASK_SERVER, 0, + attributeData, attributeType)); + } + else + { + ChipLogError(Zcl, "Failed to preapre data to write: %s", ErrorStr(preparationError)); + imCode = Protocols::InteractionModel::ProtocolCode::InvalidValue; + } + + return apWriteHandler->AddAttributeStatusCode(attributePathParams, + imCode == Protocols::InteractionModel::ProtocolCode::Success + ? Protocols::SecureChannel::GeneralStatusCode::kSuccess + : Protocols::SecureChannel::GeneralStatusCode::kFailure, + Protocols::SecureChannel::Id, imCode); +} + } // namespace app } // namespace chip diff --git a/src/app/zap-templates/app-templates.json b/src/app/zap-templates/app-templates.json index 0c3a1470f77960..09a05783977f14 100644 --- a/src/app/zap-templates/app-templates.json +++ b/src/app/zap-templates/app-templates.json @@ -5,6 +5,7 @@ "partials/helper.js", "common/ListHelper.js", "common/StringHelper.js", + "common/ChipTypesHelper.js", "templates/app/helper.js", "templates/chip/helper.js" ], diff --git a/src/app/zap-templates/templates/app/CHIPClientCallbacks-src.zapt b/src/app/zap-templates/templates/app/CHIPClientCallbacks-src.zapt index 51d6a2826d0233..226fb756110310 100644 --- a/src/app/zap-templates/templates/app/CHIPClientCallbacks-src.zapt +++ b/src/app/zap-templates/templates/app/CHIPClientCallbacks-src.zapt @@ -113,6 +113,9 @@ constexpr uint16_t kByteSpanSizeLengthInBytes = 2; return true; \ } +#define GET_ATTRIBUTE_RESPONSE_CALLBACKS(name) \ + + #define GET_REPORT_CALLBACK(name) \ Callback::Cancelable * onReportCallback = nullptr; \ CHIP_ERROR err = gCallbacks.GetReportCallback(sourceId, endpointId, clusterId, attributeId, &onReportCallback); \ @@ -412,6 +415,48 @@ bool IMDefaultResponseCallback(const chip::app::Command * commandObj, EmberAfSta return true; } +bool IMWriteResponseCallback(const chip::app::WriteClient * writeClient, EmberAfStatus status) +{ + ChipLogProgress(Zcl, "WriteResponse:"); + LogStatus(status); + + Callback::Cancelable * onSuccessCallback = nullptr; \ + Callback::Cancelable * onFailureCallback = nullptr; \ + NodeId sourceNodeId = writeClient->GetSourceNodeId(); \ + uint8_t seq = static_cast(writeClient->GetAppIdentifier()); \ + CHIP_ERROR err = gCallbacks.GetResponseCallback(sourceNodeId, seq, &onSuccessCallback, &onFailureCallback); \ + \ + if (CHIP_NO_ERROR != err) \ + { \ + if (onSuccessCallback == nullptr) \ + { \ + ChipLogDetail(Zcl, "%s: Missing success callback", name); \ + } \ + \ + if (onFailureCallback == nullptr) \ + { \ + ChipLogDetail(Zcl, "%s: Missing failure callback", name); \ + } \ + \ + return true; \ + } + + if (status == EMBER_ZCL_STATUS_SUCCESS) + { + Callback::Callback * cb = + Callback::Callback::FromCancelable(onSuccessCallback); + cb->mCall(cb->mContext); + } + else + { + Callback::Callback * cb = + Callback::Callback::FromCancelable(onFailureCallback); + cb->mCall(cb->mContext, static_cast(status)); + } + + return true; +} + bool IMReadReportAttributesResponseCallback(const app::ReadClient * apReadClient, const app::ClusterInfo & aPath, TLV::TLVReader * apData, Protocols::InteractionModel::ProtocolCode status) { diff --git a/src/app/zap-templates/templates/app/CHIPClientCallbacks.zapt b/src/app/zap-templates/templates/app/CHIPClientCallbacks.zapt index fc8a2403224aff..66a128e6e9003e 100644 --- a/src/app/zap-templates/templates/app/CHIPClientCallbacks.zapt +++ b/src/app/zap-templates/templates/app/CHIPClientCallbacks.zapt @@ -17,6 +17,7 @@ bool IMDefaultResponseCallback(const chip::app::Command * commandObj, EmberAfStatus status); bool IMReadReportAttributesResponseCallback(const chip::app::ReadClient * apReadClient, const chip::app::ClusterInfo & aPath, chip::TLV::TLVReader * apData, chip::Protocols::InteractionModel::ProtocolCode status); +bool IMWriteResponseCallback(const chip::app::WriteClient * writeClient, EmberAfStatus status); // Global Response Callbacks typedef void (*DefaultSuccessCallback)(void * context); diff --git a/src/app/zap-templates/templates/app/CHIPClusters-src.zapt b/src/app/zap-templates/templates/app/CHIPClusters-src.zapt index 75c009c118d723..64eb8db9083c98 100644 --- a/src/app/zap-templates/templates/app/CHIPClusters-src.zapt +++ b/src/app/zap-templates/templates/app/CHIPClusters-src.zapt @@ -15,6 +15,9 @@ #include #include +#include +#include +#include #define COMMAND_HEADER(name, clusterId) \ const char * kName = name; \ @@ -65,6 +68,11 @@ namespace Controller { // TODO(#4503): length should be passed to commands when byte string is in argument list. // TODO(#4503): Commands should take group id as an argument. +namespace { + +// The helper function to encode a single scalar (numeric / string / bytearray) attributes. +} + {{#chip_client_clusters}} // {{asUpperCamelCase name}} Cluster Commands @@ -140,31 +148,18 @@ CHIP_ERROR {{asUpperCamelCase parent.name}}Cluster::ReadAttribute{{asUpperCamelC } {{#if isWritableAttribute}} -CHIP_ERROR {{asUpperCamelCase parent.name}}Cluster::WriteAttribute{{asUpperCamelCase name}}(Callback::Cancelable * onSuccessCallback, Callback::Cancelable * onFailureCallback, {{chipType}} {{asLowerCamelCase name}}) +CHIP_ERROR {{asUpperCamelCase parent.name}}Cluster::WriteAttribute{{asUpperCamelCase name}}(Callback::Cancelable * onSuccessCallback, Callback::Cancelable * onFailureCallback, {{chipType}} value) { - COMMAND_HEADER("Write{{asUpperCamelCase parent.name}}{{asUpperCamelCase name}}", {{asUpperCamelCase parent.name}}::Id); - {{#if (isString type)}} - size_t {{asLowerCamelCase name}}StrLen = {{asLowerCamelCase name}}.size(); - if (!CanCastTo<{{#if (isShortString type)}}uint8_t{{else}}uint16_t{{/if}}>({{asLowerCamelCase name}}StrLen)) - { - ChipLogError(Zcl, "Error encoding %s command. String too long: %zu", kName, {{asLowerCamelCase name}}StrLen); - return CHIP_ERROR_INTERNAL; - } + app::WriteClientHandle handle; + chip::app::AttributePathParams attributePath; + attributePath.mNodeId = mDevice->GetDeviceId(); + attributePath.mEndpointId = mEndpoint; + attributePath.mClusterId = mClusterId; + attributePath.mFieldId = {{ asHex code 8 }}; + attributePath.mFlags.Set(chip::app::AttributePathParams::Flags::kFieldIdValid); + ReturnErrorOnFailure(handle.EncodeScalarAttributeWritePayload(attributePath, value)); - {{/if}} - buf.Put8(kFrameControlGlobalCommand) - .Put8(seqNum) - .Put32(Globals::Commands::Ids::WriteAttributes) - .Put32({{#if isGlobalAttribute}}Globals{{else}}{{asUpperCamelCase parent.name}}{{/if}}::Attributes::Ids::{{asUpperCamelCase name}}) - .Put8({{atomicTypeId}}) - {{#if (isString type)}} - .Put{{#if (isLongString type)}}16{{/if}}(static_cast<{{#if (isShortString type)}}uint8_t{{else}}uint16_t{{/if}}>({{asLowerCamelCase name}}StrLen)) - .Put({{asLowerCamelCase name}}.data(), {{asLowerCamelCase name}}StrLen) - {{else}} - .Put{{chipTypePutLength}}(static_cast<{{chipTypePutCastType}}>({{asLowerCamelCase name}})) - {{/if}} - ; - COMMAND_FOOTER(); + return mDevice->SendWriteAttributeRequest(std::move(handle), onSuccessCallback, onFailureCallback); } {{/if}} diff --git a/src/controller/CHIPDevice.cpp b/src/controller/CHIPDevice.cpp index faa2ccde394e7a..4bfade8b23e43e 100644 --- a/src/controller/CHIPDevice.cpp +++ b/src/controller/CHIPDevice.cpp @@ -697,6 +697,27 @@ CHIP_ERROR Device::SendReadAttributeRequest(app::AttributePathParams aPath, Call return err; } +CHIP_ERROR Device::SendWriteAttributeRequest(app::WriteClientHandle aHandle, Callback::Cancelable * onSuccessCallback, + Callback::Cancelable * onFailureCallback) +{ + bool loadedSecureSession = false; + uint8_t seqNum = GetNextSequenceNumber(); + CHIP_ERROR err = CHIP_NO_ERROR; + + aHandle->SetAppIdentifier(seqNum); + ReturnErrorOnFailure(LoadSecureSessionParametersIfNeeded(loadedSecureSession)); + + if (onSuccessCallback != nullptr || onFailureCallback != nullptr) + { + AddResponseHandler(seqNum, onSuccessCallback, onFailureCallback); + } + if ((err = aHandle.SendWriteRequest(GetDeviceId(), 0, &mSecureSession)) != CHIP_NO_ERROR) + { + CancelResponseHandler(seqNum); + } + return err; +} + Device::~Device() { if (mExchangeMgr) diff --git a/src/controller/CHIPDevice.h b/src/controller/CHIPDevice.h index f322a0943b5058..8116a586b8da57 100644 --- a/src/controller/CHIPDevice.h +++ b/src/controller/CHIPDevice.h @@ -151,6 +151,9 @@ class DLL_EXPORT Device : public Messaging::ExchangeDelegate, public SessionEsta CHIP_ERROR SendReadAttributeRequest(app::AttributePathParams aPath, Callback::Cancelable * onSuccessCallback, Callback::Cancelable * onFailureCallback, app::TLVDataFilter aTlvDataFilter); + CHIP_ERROR SendWriteAttributeRequest(app::WriteClientHandle aHandle, Callback::Cancelable * onSuccessCallback, + Callback::Cancelable * onFailureCallback); + /** * @brief * Send the command in internal command sender. diff --git a/src/controller/CHIPDeviceController.cpp b/src/controller/CHIPDeviceController.cpp index 7bc3a22bfcf5f7..78956fd969bf2c 100644 --- a/src/controller/CHIPDeviceController.cpp +++ b/src/controller/CHIPDeviceController.cpp @@ -45,6 +45,7 @@ #include #include +#include #include #include #include @@ -1672,6 +1673,36 @@ CHIP_ERROR DeviceControllerInteractionModelDelegate::ReportError(const app::Read return CHIP_NO_ERROR; } +CHIP_ERROR DeviceControllerInteractionModelDelegate::WriteResponseStatus( + const app::WriteClient * apWriteClient, const Protocols::SecureChannel::GeneralStatusCode aGeneralCode, + const uint32_t aProtocolId, const uint16_t aProtocolCode, app::AttributePathParams & aAttributePathParams, + uint8_t aCommandIndex) +{ +#if !CHIP_DEVICE_CONFIG_ENABLE_BOTH_COMMISSIONER_AND_COMMISSIONEE // temporary - until example app clusters are updated (Issue 8347) + IMWriteResponseCallback(apWriteClient, chip::app::ToEmberAfStatus(Protocols::InteractionModel::ProtocolCode(aProtocolCode))); +#endif + return CHIP_NO_ERROR; +} + +CHIP_ERROR DeviceControllerInteractionModelDelegate::WriteResponseProtocolError(const app::WriteClient * apWriteClient, + uint8_t aAttributeIndex) +{ +#if !CHIP_DEVICE_CONFIG_ENABLE_BOTH_COMMISSIONER_AND_COMMISSIONEE // temporary - until example app clusters are updated (Issue 8347) + // When WriteResponseProtocolError occurred, it means server returned an invalid packet. + IMWriteResponseCallback(apWriteClient, EMBER_ZCL_STATUS_FAILURE); +#endif + return CHIP_NO_ERROR; +} + +CHIP_ERROR DeviceControllerInteractionModelDelegate::WriteResponseError(const app::WriteClient * apWriteClient, CHIP_ERROR aError) +{ +#if !CHIP_DEVICE_CONFIG_ENABLE_BOTH_COMMISSIONER_AND_COMMISSIONEE // temporary - until example app clusters are updated (Issue 8347) + // When WriteResponseError occurred, it means we failed to receive the response from server. + IMWriteResponseCallback(apWriteClient, EMBER_ZCL_STATUS_FAILURE); +#endif + return CHIP_NO_ERROR; +} + void BasicSuccess(void * context, uint16_t val) { ChipLogProgress(Controller, "Received success response 0x%x\n", val); diff --git a/src/controller/CHIPDeviceController.h b/src/controller/CHIPDeviceController.h index c0680d69cec1c7..98f4666f5e6c75 100644 --- a/src/controller/CHIPDeviceController.h +++ b/src/controller/CHIPDeviceController.h @@ -180,6 +180,15 @@ class DeviceControllerInteractionModelDelegate : public chip::app::InteractionMo void OnReportData(const app::ReadClient * apReadClient, const app::ClusterInfo & aPath, TLV::TLVReader * apData, Protocols::InteractionModel::ProtocolCode status) override; CHIP_ERROR ReportError(const app::ReadClient * apReadClient, CHIP_ERROR aError) override; + + CHIP_ERROR WriteResponseStatus(const app::WriteClient * apWriteClient, + const Protocols::SecureChannel::GeneralStatusCode aGeneralCode, const uint32_t aProtocolId, + const uint16_t aProtocolCode, app::AttributePathParams & aAttributePathParams, + uint8_t aCommandIndex) override; + + CHIP_ERROR WriteResponseProtocolError(const app::WriteClient * apWriteClient, uint8_t aAttributeIndex) override; + + CHIP_ERROR WriteResponseError(const app::WriteClient * apWriteClient, CHIP_ERROR aError) override; }; /** diff --git a/src/lib/core/CHIPTLV.h b/src/lib/core/CHIPTLV.h index 94c0e90fd52008..680eb60872d944 100644 --- a/src/lib/core/CHIPTLV.h +++ b/src/lib/core/CHIPTLV.h @@ -1159,6 +1159,18 @@ class DLL_EXPORT TLVWriter */ CHIP_ERROR PutBoolean(uint64_t tag, bool v); + /** + * @overload CHIP_ERROR TLVWriter::Put(uint64_t tag, bool v) + */ + CHIP_ERROR Put(uint64_t tag, bool v) + { + /* + * In TLV, boolean values are encoded as standalone tags without actual values, so we have a seperate + * PutBoolean method. + */ + return PutBoolean(tag, v); + } + /** * Encodes a TLV byte string value. *