Matter SDK Coverage Report
Current view: top level - app - WriteClient.h (source / functions) Coverage Total Hit
Test: SHA:b879ecb8e99e175eea0a293a888bda853da2b19c Lines: 0.0 % 22 0
Test Date: 2025-01-17 19:00:11 Functions: 0.0 % 8 0

            Line data    Source code
       1              : /*
       2              :  *
       3              :  *    Copyright (c) 2021 Project CHIP Authors
       4              :  *    All rights reserved.
       5              :  *
       6              :  *    Licensed under the Apache License, Version 2.0 (the "License");
       7              :  *    you may not use this file except in compliance with the License.
       8              :  *    You may obtain a copy of the License at
       9              :  *
      10              :  *        http://www.apache.org/licenses/LICENSE-2.0
      11              :  *
      12              :  *    Unless required by applicable law or agreed to in writing, software
      13              :  *    distributed under the License is distributed on an "AS IS" BASIS,
      14              :  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      15              :  *    See the License for the specific language governing permissions and
      16              :  *    limitations under the License.
      17              :  */
      18              : 
      19              : #pragma once
      20              : 
      21              : #include <app/AttributePathParams.h>
      22              : #include <app/ConcreteAttributePath.h>
      23              : #include <app/InteractionModelTimeout.h>
      24              : #include <app/MessageDef/AttributeDataIBs.h>
      25              : #include <app/MessageDef/AttributeStatusIB.h>
      26              : #include <app/MessageDef/StatusIB.h>
      27              : #include <app/MessageDef/WriteRequestMessage.h>
      28              : #include <app/data-model/Encode.h>
      29              : #include <app/data-model/FabricScoped.h>
      30              : #include <app/data-model/List.h>
      31              : #include <lib/core/CHIPCore.h>
      32              : #include <lib/core/TLVDebug.h>
      33              : #include <lib/support/CodeUtils.h>
      34              : #include <lib/support/DLLUtil.h>
      35              : #include <lib/support/logging/CHIPLogging.h>
      36              : #include <messaging/ExchangeHolder.h>
      37              : #include <messaging/ExchangeMgr.h>
      38              : #include <messaging/Flags.h>
      39              : #include <platform/LockTracker.h>
      40              : #include <protocols/Protocols.h>
      41              : #include <system/SystemPacketBuffer.h>
      42              : #include <system/TLVPacketBufferBackingStore.h>
      43              : 
      44              : namespace chip {
      45              : namespace app {
      46              : 
      47              : class InteractionModelEngine;
      48              : 
      49              : /**
      50              :  *  @brief The write client represents the initiator side of a Write Interaction, and is responsible
      51              :  *  for generating one Write Request for a particular set of attributes, and handling the Write response.
      52              :  *  Consumer can allocate one write client, then call PrepareAttribute, insert attribute value, followed
      53              :  *  by FinishAttribute for every attribute it wants to insert in write request, then call SendWriteRequest
      54              :  *
      55              :  *  Note: When writing lists, you may receive multiple write status responses for a single list.
      56              :  *  Please see ChunkedWriteCallback.h for a high level API which will merge status codes for
      57              :  *  chunked write requests.
      58              :  *
      59              :  */
      60              : class WriteClient : public Messaging::ExchangeDelegate
      61              : {
      62              : public:
      63              :     class Callback
      64              :     {
      65              :     public:
      66            0 :         virtual ~Callback() = default;
      67              : 
      68              :         /**
      69              :          * OnResponse will be called when a write response has been received
      70              :          * and processed for the given path.
      71              :          *
      72              :          * The WriteClient object MUST continue to exist after this call is completed. The application shall wait until it
      73              :          * receives an OnDone call before it shuts down the object.
      74              :          *
      75              :          * @param[in] apWriteClient   The write client object that initiated the write transaction.
      76              :          * @param[in] aPath           The attribute path field in write response.
      77              :          * @param[in] attributeStatus Attribute-specific status, containing an InteractionModel::Status code as well as
      78              :          *                            an optional cluster-specific status code.
      79              :          */
      80            0 :         virtual void OnResponse(const WriteClient * apWriteClient, const ConcreteDataAttributePath & aPath,
      81              :                                 StatusIB attributeStatus)
      82            0 :         {}
      83              : 
      84              :         /**
      85              :          * OnError will be called when an error occurs *after* a successful call to SendWriteRequest(). The following
      86              :          * errors will be delivered through this call in the aError field:
      87              :          *
      88              :          * - CHIP_ERROR_TIMEOUT: A response was not received within the expected response timeout.
      89              :          * - CHIP_ERROR_*TLV*: A malformed, non-compliant response was received from the server.
      90              :          * - CHIP_ERROR encapsulating a StatusIB: If we got a non-path-specific
      91              :          *   status response from the server.  In that case, constructing
      92              :          *   a StatusIB from the error can be used to extract the status.
      93              :          * - CHIP_ERROR*: All other cases.
      94              :          *
      95              :          * The WriteClient object MUST continue to exist after this call is completed. The application shall wait until it
      96              :          * receives an OnDone call before it shuts down the object.
      97              :          *
      98              :          * @param[in] apWriteClient The write client object that initiated the attribute write transaction.
      99              :          * @param[in] aError        A system error code that conveys the overall error code.
     100              :          */
     101            0 :         virtual void OnError(const WriteClient * apWriteClient, CHIP_ERROR aError) {}
     102              : 
     103              :         /**
     104              :          * OnDone will be called when WriteClient has finished all work and is reserved for future WriteClient ownership change.
     105              :          * (#10366) Users may use this function to release their own objects related to this write interaction.
     106              :          *
     107              :          * This function will:
     108              :          *      - Always be called exactly *once* for a given WriteClient instance.
     109              :          *      - Be called even in error circumstances.
     110              :          *      - Only be called after a successful call to SendWriteRequest has been made.
     111              :          *
     112              :          * @param[in] apWriteClient The write client object of the terminated write transaction.
     113              :          */
     114              :         virtual void OnDone(WriteClient * apWriteClient) = 0;
     115              :     };
     116              : 
     117              :     /**
     118              :      *  Construct the client object. Within the lifetime
     119              :      *  of this instance.
     120              :      *
     121              :      *  @param[in]    apExchangeMgr    A pointer to the ExchangeManager object.
     122              :      *  @param[in]    apCallback       Callback set by application.
     123              :      *  @param[in]    aTimedWriteTimeoutMs If provided, do a timed write using this timeout.
     124              :      *  @param[in]    aSuppressResponse If provided, set SuppressResponse field to the provided value
     125              :      */
     126              :     WriteClient(Messaging::ExchangeManager * apExchangeMgr, Callback * apCallback, const Optional<uint16_t> & aTimedWriteTimeoutMs,
     127              :                 bool aSuppressResponse = false) :
     128              :         mpExchangeMgr(apExchangeMgr),
     129              :         mExchangeCtx(*this), mpCallback(apCallback), mTimedWriteTimeoutMs(aTimedWriteTimeoutMs),
     130              :         mSuppressResponse(aSuppressResponse)
     131              :     {
     132              :         assertChipStackLockedByCurrentThread();
     133              :     }
     134              : 
     135              : #if CONFIG_BUILD_FOR_HOST_UNIT_TEST
     136              :     WriteClient(Messaging::ExchangeManager * apExchangeMgr, Callback * apCallback, const Optional<uint16_t> & aTimedWriteTimeoutMs,
     137              :                 uint16_t aReservedSize) :
     138              :         mpExchangeMgr(apExchangeMgr),
     139              :         mExchangeCtx(*this), mpCallback(apCallback), mTimedWriteTimeoutMs(aTimedWriteTimeoutMs), mReservedSize(aReservedSize)
     140              :     {
     141              :         assertChipStackLockedByCurrentThread();
     142              :     }
     143              : #endif
     144              : 
     145            0 :     ~WriteClient() { assertChipStackLockedByCurrentThread(); }
     146              : 
     147              :     /**
     148              :      *  Encode an attribute value that can be directly encoded using DataModel::Encode. Will create a new chunk when necessary.
     149              :      */
     150              :     template <class T>
     151              :     CHIP_ERROR EncodeAttribute(const AttributePathParams & attributePath, const T & value,
     152              :                                const Optional<DataVersion> & aDataVersion = NullOptional)
     153              :     {
     154              :         ReturnErrorOnFailure(EnsureMessage());
     155              : 
     156              :         // Here, we are using kInvalidEndpointId for missing endpoint id, which is used when sending group write requests.
     157              :         return EncodeSingleAttributeDataIB(
     158              :             ConcreteDataAttributePath(attributePath.HasWildcardEndpointId() ? kInvalidEndpointId : attributePath.mEndpointId,
     159              :                                       attributePath.mClusterId, attributePath.mAttributeId, aDataVersion),
     160              :             value);
     161              :     }
     162              : 
     163              :     /**
     164              :      *  Encode a possibly-chunked list attribute value.  Will create a new chunk when necessary.
     165              :      */
     166              :     template <class T>
     167              :     CHIP_ERROR EncodeAttribute(const AttributePathParams & attributePath, const DataModel::List<T> & value,
     168              :                                const Optional<DataVersion> & aDataVersion = NullOptional)
     169              :     {
     170              :         // Here, we are using kInvalidEndpointId for missing endpoint id, which is used when sending group write requests.
     171              :         ConcreteDataAttributePath path =
     172              :             ConcreteDataAttributePath(attributePath.HasWildcardEndpointId() ? kInvalidEndpointId : attributePath.mEndpointId,
     173              :                                       attributePath.mClusterId, attributePath.mAttributeId, aDataVersion);
     174              : 
     175              :         ReturnErrorOnFailure(EnsureMessage());
     176              : 
     177              :         // Encode an empty list for the chunking protocol.
     178              :         ReturnErrorOnFailure(EncodeSingleAttributeDataIB(path, DataModel::List<uint8_t>()));
     179              : 
     180              :         path.mListOp = ConcreteDataAttributePath::ListOperation::AppendItem;
     181              :         for (size_t i = 0; i < value.size(); i++)
     182              :         {
     183              :             ReturnErrorOnFailure(EncodeSingleAttributeDataIB(path, value.data()[i]));
     184              :         }
     185              : 
     186              :         return CHIP_NO_ERROR;
     187              :     }
     188              : 
     189              :     /**
     190              :      * Encode a Nullable attribute value.  This needs a separate overload so it can dispatch to the right
     191              :      * EncodeAttribute when writing a nullable list.
     192              :      */
     193              :     template <class T>
     194              :     CHIP_ERROR EncodeAttribute(const AttributePathParams & attributePath, const DataModel::Nullable<T> & value,
     195              :                                const Optional<DataVersion> & aDataVersion = NullOptional)
     196              :     {
     197              :         ReturnErrorOnFailure(EnsureMessage());
     198              : 
     199              :         if (value.IsNull())
     200              :         {
     201              :             // Here, we are using kInvalidEndpointId to for missing endpoint id, which is used when sending group write requests.
     202              :             return EncodeSingleAttributeDataIB(
     203              :                 ConcreteDataAttributePath(attributePath.HasWildcardEndpointId() ? kInvalidEndpointId : attributePath.mEndpointId,
     204              :                                           attributePath.mClusterId, attributePath.mAttributeId, aDataVersion),
     205              :                 value);
     206              :         }
     207              : 
     208              :         return EncodeAttribute(attributePath, value.Value(), aDataVersion);
     209              :     }
     210              : 
     211              :     /**
     212              :      * Encode an attribute value which is already encoded into a TLV. The TLVReader is expected to be initialized and the read head
     213              :      * is expected to point to the element to be encoded.
     214              :      *
     215              :      * Note: When encoding lists with this function, you may receive more than one write status for a single list. You can refer
     216              :      * to ChunkedWriteCallback.h for a high level API which will merge status codes for chunked write requests.
     217              :      */
     218              :     CHIP_ERROR PutPreencodedAttribute(const ConcreteDataAttributePath & attributePath, const TLV::TLVReader & data);
     219              : 
     220              :     /**
     221              :      *  Once SendWriteRequest returns successfully, the WriteClient will
     222              :      *  handle calling Shutdown on itself once it decides it's done with waiting
     223              :      *  for a response (i.e. times out or gets a response). Client can specify
     224              :      *  the maximum time to wait for response (in milliseconds) via timeout parameter.
     225              :      *  If the timeout is missing or is set to System::Clock::kZero, a value based on the MRP timeouts of the session will be used.
     226              :      *  If SendWriteRequest is never called, or the call fails, the API
     227              :      *  consumer is responsible for calling Shutdown on the WriteClient.
     228              :      */
     229              :     CHIP_ERROR SendWriteRequest(const SessionHandle & session, System::Clock::Timeout timeout = System::Clock::kZero);
     230              : 
     231              : private:
     232              :     friend class TestWriteInteraction;
     233              :     friend class InteractionModelEngine;
     234              : 
     235              :     enum class State
     236              :     {
     237              :         Initialized = 0,     // The client has been initialized
     238              :         AddAttribute,        // The client has added attribute and ready for a SendWriteRequest
     239              :         AwaitingTimedStatus, // Sent a Tiemd Request, waiting for response.
     240              :         AwaitingResponse,    // The client has sent out the write request message
     241              :         ResponseReceived,    // We have gotten a response after sending write request
     242              :         AwaitingDestruction, // The object has completed its work and is awaiting destruction by the application.
     243              :     };
     244              : 
     245              :     CHIP_ERROR OnMessageReceived(Messaging::ExchangeContext * apExchangeContext, const PayloadHeader & aPayloadHeader,
     246              :                                  System::PacketBufferHandle && aPayload) override;
     247              :     void OnResponseTimeout(Messaging::ExchangeContext * apExchangeContext) override;
     248              : 
     249              :     void MoveToState(const State aTargetState);
     250              :     CHIP_ERROR ProcessWriteResponseMessage(System::PacketBufferHandle && payload);
     251              :     CHIP_ERROR ProcessAttributeStatusIB(AttributeStatusIB::Parser & aAttributeStatusIB);
     252              :     const char * GetStateStr() const;
     253              : 
     254              :     /**
     255              :      *  Encode an attribute value that can be directly encoded using DataModel::Encode.
     256              :      */
     257              :     template <class T, std::enable_if_t<!DataModel::IsFabricScoped<T>::value, int> = 0>
     258            0 :     CHIP_ERROR TryEncodeSingleAttributeDataIB(const ConcreteDataAttributePath & attributePath, const T & value)
     259              :     {
     260            0 :         chip::TLV::TLVWriter * writer = nullptr;
     261              : 
     262            0 :         ReturnErrorOnFailure(PrepareAttributeIB(attributePath));
     263            0 :         VerifyOrReturnError((writer = GetAttributeDataIBTLVWriter()) != nullptr, CHIP_ERROR_INCORRECT_STATE);
     264            0 :         ReturnErrorOnFailure(DataModel::Encode(*writer, chip::TLV::ContextTag(chip::app::AttributeDataIB::Tag::kData), value));
     265            0 :         ReturnErrorOnFailure(FinishAttributeIB());
     266              : 
     267            0 :         return CHIP_NO_ERROR;
     268              :     }
     269              : 
     270              :     template <class T, std::enable_if_t<DataModel::IsFabricScoped<T>::value, int> = 0>
     271              :     CHIP_ERROR TryEncodeSingleAttributeDataIB(const ConcreteDataAttributePath & attributePath, const T & value)
     272              :     {
     273              :         chip::TLV::TLVWriter * writer = nullptr;
     274              : 
     275              :         ReturnErrorOnFailure(PrepareAttributeIB(attributePath));
     276              :         VerifyOrReturnError((writer = GetAttributeDataIBTLVWriter()) != nullptr, CHIP_ERROR_INCORRECT_STATE);
     277              :         ReturnErrorOnFailure(
     278              :             DataModel::EncodeForWrite(*writer, chip::TLV::ContextTag(chip::app::AttributeDataIB::Tag::kData), value));
     279              :         ReturnErrorOnFailure(FinishAttributeIB());
     280              : 
     281              :         return CHIP_NO_ERROR;
     282              :     }
     283              : 
     284              :     /**
     285              :      * A wrapper for TryEncodeSingleAttributeDataIB which will start a new chunk when failed with CHIP_ERROR_NO_MEMORY or
     286              :      * CHIP_ERROR_BUFFER_TOO_SMALL.
     287              :      */
     288              :     template <class T>
     289            0 :     CHIP_ERROR EncodeSingleAttributeDataIB(const ConcreteDataAttributePath & attributePath, const T & value)
     290              :     {
     291            0 :         TLV::TLVWriter backupWriter;
     292              : 
     293            0 :         mWriteRequestBuilder.GetWriteRequests().Checkpoint(backupWriter);
     294              : 
     295              :         // First attempt to write this attribute.
     296            0 :         CHIP_ERROR err = TryEncodeSingleAttributeDataIB(attributePath, value);
     297            0 :         if (err == CHIP_ERROR_NO_MEMORY || err == CHIP_ERROR_BUFFER_TOO_SMALL)
     298              :         {
     299              :             // If it failed with no memory, then we create a new chunk for it.
     300            0 :             mWriteRequestBuilder.GetWriteRequests().Rollback(backupWriter);
     301            0 :             ReturnErrorOnFailure(StartNewMessage());
     302            0 :             ReturnErrorOnFailure(TryEncodeSingleAttributeDataIB(attributePath, value));
     303              :         }
     304              :         else
     305              :         {
     306            0 :             ReturnErrorOnFailure(err);
     307              :         }
     308              : 
     309            0 :         return CHIP_NO_ERROR;
     310              :     }
     311              : 
     312              :     /**
     313              :      * Encode a preencoded attribute data, returns TLV encode error if the ramaining space of current chunk is too small for the
     314              :      * AttributeDataIB.
     315              :      */
     316              :     CHIP_ERROR TryPutSinglePreencodedAttributeWritePayload(const ConcreteDataAttributePath & attributePath,
     317              :                                                            const TLV::TLVReader & data);
     318              : 
     319              :     /**
     320              :      * Encode a preencoded attribute data, will try to create a new chunk when necessary.
     321              :      */
     322              :     CHIP_ERROR PutSinglePreencodedAttributeWritePayload(const ConcreteDataAttributePath & attributePath,
     323              :                                                         const TLV::TLVReader & data);
     324              : 
     325              :     CHIP_ERROR EnsureMessage();
     326              : 
     327              :     /**
     328              :      * Called internally to signal the completion of all work on this object, gracefully close the
     329              :      * exchange (by calling into the base class) and finally, signal to the application that it's
     330              :      * safe to release this object.
     331              :      */
     332              :     void Close();
     333              : 
     334              :     /**
     335              :      * This forcibly closes the exchange context if a valid one is pointed to. Such a situation does
     336              :      * not arise during normal message processing flows that all normally call Close() above. This can only
     337              :      * arise due to application-initiated destruction of the object when this object is handling receiving/sending
     338              :      * message payloads.
     339              :      */
     340              :     void Abort();
     341              : 
     342              :     // Send our queued-up Write Request message.  Assumes the exchange is ready
     343              :     // and mPendingWriteData is populated.
     344              :     CHIP_ERROR SendWriteRequest();
     345              : 
     346              :     // Encodes the header of an AttributeDataIB, a special case for attributePath is its EndpointId can be kInvalidEndpointId, this
     347              :     // is used when sending group write requests.
     348              :     // TODO(#14935) Update AttributePathParams to support more list operations.
     349              :     CHIP_ERROR PrepareAttributeIB(const ConcreteDataAttributePath & attributePath);
     350              :     CHIP_ERROR FinishAttributeIB();
     351              :     TLV::TLVWriter * GetAttributeDataIBTLVWriter();
     352              : 
     353              :     /**
     354              :      * Create a new message (or a new chunk) for the write request.
     355              :      */
     356              :     CHIP_ERROR StartNewMessage();
     357              : 
     358              :     /**
     359              :      * Finalize Write Request Message TLV Builder and retrieve final data from tlv builder for later sending
     360              :      */
     361              :     CHIP_ERROR FinalizeMessage(bool aHasMoreChunks);
     362              : 
     363              :     Messaging::ExchangeManager * mpExchangeMgr = nullptr;
     364              :     Messaging::ExchangeHolder mExchangeCtx;
     365              :     Callback * mpCallback = nullptr;
     366              :     State mState          = State::Initialized;
     367              :     System::PacketBufferTLVWriter mMessageWriter;
     368              :     WriteRequestMessage::Builder mWriteRequestBuilder;
     369              :     // TODO Maybe we should change PacketBufferTLVWriter so we can finalize it
     370              :     // but have it hold on to the buffer, and get the buffer from it later.
     371              :     // Then we could avoid this extra pointer-sized member.
     372              :     System::PacketBufferHandle mPendingWriteData;
     373              :     // If mTimedWriteTimeoutMs has a value, we are expected to do a timed
     374              :     // write.
     375              :     Optional<uint16_t> mTimedWriteTimeoutMs;
     376              :     bool mSuppressResponse = false;
     377              : 
     378              :     // A list of buffers, one buffer for each chunk.
     379              :     System::PacketBufferHandle mChunks;
     380              : 
     381              :     // TODO: This file might be compiled with different build flags on Darwin platform (when building WriteClient.cpp and
     382              :     // CHIPClustersObjc.mm), which will cause undefined behavior when building write requests. Uncomment the #if and #endif after
     383              :     // resolving it.
     384              :     // #if CONFIG_BUILD_FOR_HOST_UNIT_TEST
     385              :     uint16_t mReservedSize = 0;
     386              :     // #endif
     387              : 
     388              :     /**
     389              :      * Below we define several const variables for encoding overheads.
     390              :      * WriteRequestMessage =
     391              :      * {
     392              :      *  timedRequest = false,
     393              :      *  AttributeDataIBs =
     394              :      *  [
     395              :      *     AttributeDataIB =             \
     396              :      *     {                              |
     397              :      *        DataVersion = 0x0,          |
     398              :      *        AttributePathIB =           |
     399              :      *        {                           |
     400              :      *           Endpoint = 0x2,          |  "atomically" encoded via
     401              :      *           Cluster = 0x50f,          > EncodeAttribute or
     402              :      *           Attribute = 0x0000_0006, |  PutPreencodedAttribute
     403              :      *           ListIndex = Null,        |
     404              :      *        }                           |
     405              :      *        Data = ...                  |
     406              :      *     },                             /
     407              :      *     (...)
     408              :      *  ],                           <-- 1 byte  "end of AttributeDataIB" (end of container)
     409              :      *  moreChunkedMessages = false, <-- 2 bytes "kReservedSizeForMoreChunksFlag"
     410              :      *  InteractionModelRevision = 1,<-- 3 bytes "kReservedSizeForIMRevision"
     411              :      * }                             <-- 1 byte  "end of WriteRequestMessage" (end of container)
     412              :      */
     413              : 
     414              :     // Reserved size for the MoreChunks boolean flag, which takes up 1 byte for the control tag and 1 byte for the context tag.
     415              :     static constexpr uint16_t kReservedSizeForMoreChunksFlag = 1 + 1;
     416              :     // End Of Container (0x18) uses one byte.
     417              :     static constexpr uint16_t kReservedSizeForEndOfContainer = 1;
     418              :     // Reserved size for the uint8_t InteractionModelRevision flag, which takes up 1 byte for the control tag and 1 byte for the
     419              :     // context tag, 1 byte for value
     420              :     static constexpr uint16_t kReservedSizeForIMRevision = 1 + 1 + 1;
     421              :     // Reserved buffer for TLV level overhead (the overhead for end of AttributeDataIBs (end of container), more chunks flag, end
     422              :     // of WriteRequestMessage (another end of container)).
     423              :     static constexpr uint16_t kReservedSizeForTLVEncodingOverhead = kReservedSizeForIMRevision + kReservedSizeForMoreChunksFlag +
     424              :         kReservedSizeForEndOfContainer + kReservedSizeForEndOfContainer;
     425              :     bool mHasDataVersion = false;
     426              : };
     427              : 
     428              : } // namespace app
     429              : } // namespace chip
        

Generated by: LCOV version 2.0-1