Matter SDK Coverage Report
Current view: top level - app - WriteClient.h (source / functions) Coverage Total Hit
Test: SHA:1ac517d5414e3758d04b2b3ea364eb0de4816de2 Lines: 95.7 % 94 90
Test Date: 2026-02-04 08:12:58 Functions: 87.5 % 40 35

            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-common/zap-generated/ids/Attributes.h>
      22              : #include <app-common/zap-generated/ids/Clusters.h>
      23              : #include <app/AttributePathParams.h>
      24              : #include <app/ConcreteAttributePath.h>
      25              : #include <app/InteractionModelTimeout.h>
      26              : #include <app/MessageDef/AttributeDataIBs.h>
      27              : #include <app/MessageDef/AttributeStatusIB.h>
      28              : #include <app/MessageDef/StatusIB.h>
      29              : #include <app/MessageDef/WriteRequestMessage.h>
      30              : #include <app/data-model/Encode.h>
      31              : #include <app/data-model/FabricScoped.h>
      32              : #include <app/data-model/List.h>
      33              : #include <lib/core/CHIPCore.h>
      34              : #include <lib/core/TLVDebug.h>
      35              : #include <lib/support/CodeUtils.h>
      36              : #include <lib/support/DLLUtil.h>
      37              : #include <lib/support/logging/CHIPLogging.h>
      38              : #include <messaging/ExchangeHolder.h>
      39              : #include <messaging/ExchangeMgr.h>
      40              : #include <messaging/Flags.h>
      41              : #include <platform/LockTracker.h>
      42              : #include <protocols/Protocols.h>
      43              : #include <system/SystemPacketBuffer.h>
      44              : #include <system/TLVPacketBufferBackingStore.h>
      45              : 
      46              : namespace chip {
      47              : namespace app {
      48              : 
      49              : class InteractionModelEngine;
      50              : 
      51              : /**
      52              :  *  @brief The write client represents the initiator side of a Write Interaction, and is responsible
      53              :  *  for generating one Write Request for a particular set of attributes, and handling the Write response.
      54              :  *  Consumer can allocate one write client, then call PrepareAttribute, insert attribute value, followed
      55              :  *  by FinishAttribute for every attribute it wants to insert in write request, then call SendWriteRequest
      56              :  *
      57              :  *  Note: When writing lists, you may receive multiple write status responses for a single list.
      58              :  *  Please see ChunkedWriteCallback.h for a high level API which will merge status codes for
      59              :  *  chunked write requests.
      60              :  *
      61              :  */
      62              : class WriteClient : public Messaging::ExchangeDelegate
      63              : {
      64              : public:
      65              :     class Callback
      66              :     {
      67              :     public:
      68         1071 :         virtual ~Callback() = default;
      69              : 
      70              :         /**
      71              :          * OnResponse will be called when a write response has been received
      72              :          * and processed for the given path.
      73              :          *
      74              :          * The WriteClient object MUST continue to exist after this call is completed. The application shall wait until it
      75              :          * receives an OnDone call before it shuts down the object.
      76              :          *
      77              :          * @param[in] apWriteClient   The write client object that initiated the write transaction.
      78              :          * @param[in] aPath           The attribute path field in write response.
      79              :          * @param[in] attributeStatus Attribute-specific status, containing an InteractionModel::Status code as well as
      80              :          *                            an optional cluster-specific status code.
      81              :          */
      82            0 :         virtual void OnResponse(const WriteClient * apWriteClient, const ConcreteDataAttributePath & aPath,
      83              :                                 StatusIB attributeStatus)
      84            0 :         {}
      85              : 
      86              :         /**
      87              :          * OnError will be called when an error occurs *after* a successful call to SendWriteRequest(). The following
      88              :          * errors will be delivered through this call in the aError field:
      89              :          *
      90              :          * - CHIP_ERROR_TIMEOUT: A response was not received within the expected response timeout.
      91              :          * - CHIP_ERROR_*TLV*: A malformed, non-compliant response was received from the server.
      92              :          * - CHIP_ERROR encapsulating a StatusIB: If we got a non-path-specific
      93              :          *   status response from the server.  In that case, constructing
      94              :          *   a StatusIB from the error can be used to extract the status.
      95              :          * - CHIP_ERROR*: All other cases.
      96              :          *
      97              :          * The WriteClient object MUST continue to exist after this call is completed. The application shall wait until it
      98              :          * receives an OnDone call before it shuts down the object.
      99              :          *
     100              :          * @param[in] apWriteClient The write client object that initiated the attribute write transaction.
     101              :          * @param[in] aError        A system error code that conveys the overall error code.
     102              :          */
     103            0 :         virtual void OnError(const WriteClient * apWriteClient, CHIP_ERROR aError) {}
     104              : 
     105              :         /**
     106              :          * OnDone will be called when WriteClient has finished all work and is reserved for future WriteClient ownership change.
     107              :          * (#10366) Users may use this function to release their own objects related to this write interaction.
     108              :          *
     109              :          * This function will:
     110              :          *      - Always be called exactly *once* for a given WriteClient instance.
     111              :          *      - Be called even in error circumstances.
     112              :          *      - Only be called after a successful call to SendWriteRequest has been made.
     113              :          *
     114              :          * @param[in] apWriteClient The write client object of the terminated write transaction.
     115              :          */
     116              :         virtual void OnDone(WriteClient * apWriteClient) = 0;
     117              :     };
     118              : 
     119              :     /**
     120              :      *  Construct the client object. Within the lifetime
     121              :      *  of this instance.
     122              :      *
     123              :      *  @param[in]    apExchangeMgr    A pointer to the ExchangeManager object.
     124              :      *  @param[in]    apCallback       Callback set by application.
     125              :      *  @param[in]    aTimedWriteTimeoutMs If provided, do a timed write using this timeout.
     126              :      *  @param[in]    aSuppressResponse If provided, set SuppressResponse field to the provided value
     127              :      */
     128          717 :     WriteClient(Messaging::ExchangeManager * apExchangeMgr, Callback * apCallback, const Optional<uint16_t> & aTimedWriteTimeoutMs,
     129          717 :                 bool aSuppressResponse = false) :
     130          717 :         mpExchangeMgr(apExchangeMgr),
     131          717 :         mExchangeCtx(*this), mpCallback(apCallback), mTimedWriteTimeoutMs(aTimedWriteTimeoutMs),
     132         1434 :         mSuppressResponse(aSuppressResponse), mTimedRequestFieldValue(aTimedWriteTimeoutMs.HasValue())
     133              :     {
     134          717 :         assertChipStackLockedByCurrentThread();
     135          717 :     }
     136              : 
     137              : #if CONFIG_BUILD_FOR_HOST_UNIT_TEST
     138          346 :     WriteClient(Messaging::ExchangeManager * apExchangeMgr, Callback * apCallback, const Optional<uint16_t> & aTimedWriteTimeoutMs,
     139          346 :                 uint16_t aReservedSize) :
     140          346 :         mpExchangeMgr(apExchangeMgr),
     141          346 :         mExchangeCtx(*this), mpCallback(apCallback), mTimedWriteTimeoutMs(aTimedWriteTimeoutMs), mReservedSize(aReservedSize),
     142          692 :         mTimedRequestFieldValue(aTimedWriteTimeoutMs.HasValue())
     143              :     {
     144          346 :         assertChipStackLockedByCurrentThread();
     145          346 :     }
     146              : 
     147              :     // Tag type to distinguish the test constructor from the normal constructor
     148              :     struct TestOnlyOverrideTimedRequestFieldTag
     149              :     {
     150              :     };
     151              : 
     152              :     /**
     153              :      * TestOnly constructor that decouples the Timed Request action from the TimedRequest field value.
     154              :      *
     155              :      * IMPORTANT: Understanding the distinction between two concepts:
     156              :      * 1. TIMED REQUEST ACTION: A preceding TimedRequest protocol message sent before the actual Write Request.
     157              :      *                          This establishes a time window during which the server will accept the write.
     158              :      *                          This is controlled by the mTimedWriteTimeoutMs field.
     159              :      *
     160              :      * 2. TIMEDREQUEST FIELD: A boolean field in the WriteRequest message itself that indicates whether
     161              :      *                       the write was preceded by a Timed Request action.
     162              :      *                       This is controlled by the mTimedRequestFieldValue field.
     163              :      *
     164              :      * Normal behavior: When you provide a timeout value to the standard constructor, both happen together:
     165              :      *   - A Timed Request action is sent (controlled by mTimedWriteTimeoutMs)
     166              :      *   - The TimedRequest field in WriteRequest is set to true (mTimedRequestFieldValue = true)
     167              :      *
     168              :      * This test constructor allows you to decouple these for testing all edge cases:
     169              :      *
     170              :      * Test scenarios enabled by this constructor:
     171              :      * 1. Normal write (both false):              Action = No,  Field = False  [aTimedWriteTimeoutMs = Missing,
     172              :      * aTimedRequestFieldValue = false]
     173              :      * 2. Normal timed write (both true):         Action = Yes, Field = True   [aTimedWriteTimeoutMs = value,
     174              :      * aTimedRequestFieldValue = true]
     175              :      * 3. Field true, no action (invalid):        Action = No,  Field = True   [aTimedWriteTimeoutMs = Missing,
     176              :      * aTimedRequestFieldValue = true]
     177              :      * 4. Action present, field false (invalid):  Action = Yes, Field = False  [aTimedWriteTimeoutMs = value,
     178              :      * aTimedRequestFieldValue = false]
     179              :      *
     180              :      * @param[in] aTimedWriteTimeoutMs The timeout for the Timed Request action (if provided, action WILL be sent)
     181              :      * @param[in] aTimedRequestFieldValue The value of the TimedRequest field in WriteRequest (can mismatch the action for testing)
     182              :      */
     183              :     WriteClient(Messaging::ExchangeManager * apExchangeMgr, Callback * apCallback, const Optional<uint16_t> & aTimedWriteTimeoutMs,
     184              :                 bool aTimedRequestFieldValue, TestOnlyOverrideTimedRequestFieldTag) :
     185              :         mpExchangeMgr(apExchangeMgr),
     186              :         mExchangeCtx(*this), mpCallback(apCallback), mTimedWriteTimeoutMs(aTimedWriteTimeoutMs),
     187              :         mTimedRequestFieldValue(aTimedRequestFieldValue)
     188              :     {
     189              :         assertChipStackLockedByCurrentThread();
     190              :     }
     191              : #endif
     192              : 
     193         1119 :     ~WriteClient() { assertChipStackLockedByCurrentThread(); }
     194              : 
     195              :     /**
     196              :      *  Encode an attribute value that can be directly encoded using DataModel::Encode. Will create a new chunk when necessary.
     197              :      */
     198              :     template <class T>
     199           19 :     CHIP_ERROR EncodeAttribute(const AttributePathParams & attributePath, const T & value,
     200              :                                const Optional<DataVersion> & aDataVersion = NullOptional)
     201              :     {
     202           19 :         ReturnErrorOnFailure(EnsureMessage());
     203              : 
     204              :         // Here, we are using kInvalidEndpointId for missing endpoint id, which is used when sending group write requests.
     205           19 :         return EncodeSingleAttributeDataIB(
     206           38 :             ConcreteDataAttributePath(attributePath.HasWildcardEndpointId() ? kInvalidEndpointId : attributePath.mEndpointId,
     207           19 :                                       attributePath.mClusterId, attributePath.mAttributeId, aDataVersion),
     208           19 :             value);
     209              :     }
     210              : 
     211              :     /**
     212              :      *  Encode a possibly-chunked list attribute value.  Will create a new chunk when necessary.
     213              :      *
     214              :      * Note: As an exception, for attributes in the Access Control cluster, this method will attempt to encode as many list items
     215              :      * as possible into a single AttributeDataIB with Change set to REPLACE.
     216              :      * If the list is too large, the WriteRequest will be chunked and remaining items will be encoded as individual AttributeDataIBs
     217              :      * with Change set to ADD, chunking them as needed.
     218              :      *
     219              :      */
     220              :     template <class T>
     221          875 :     CHIP_ERROR EncodeAttribute(const AttributePathParams & attributePath, const DataModel::List<T> & listValue,
     222              :                                const Optional<DataVersion> & aDataVersion = NullOptional)
     223              :     {
     224              :         // Here, we are using kInvalidEndpointId for missing endpoint id, which is used when sending group write requests.
     225         1750 :         ConcreteDataAttributePath path =
     226          875 :             ConcreteDataAttributePath(attributePath.HasWildcardEndpointId() ? kInvalidEndpointId : attributePath.mEndpointId,
     227          875 :                                       attributePath.mClusterId, attributePath.mAttributeId, aDataVersion);
     228              : 
     229          875 :         ListIndex firstItemToAppendIndex = 0;
     230          875 :         uint16_t encodedItemCount        = 0;
     231          875 :         bool chunkingNeeded              = false;
     232              : 
     233              :         // By convention, and as tested against all cluster servers, clients have historically encoded an empty list as a
     234              :         // ReplaceAll, (i.e. the entire attribute contents are cleared before appending the new list’s items). However, this
     235              :         // behavior can be problematic, especially for the ACL attribute; sending an empty ReplaceAll list can cause clients to be
     236              :         // locked out. This is because the empty list first deletes all existing ACL entries, and if the new (malformed) ACL is
     237              :         // rejected, the server is left without valid (or with incomplete) ACLs.
     238              :         // SOLUTION: we treat ACL as an exception and avoid encoding an empty ReplaceAll list. Instead, we pack as many ACL entries
     239              :         // as possible into the ReplaceAll list, and send  any remaining entries in subsequent chunks are part of the AppendItem
     240              :         // list operation.
     241              :         // TODO (#38270): Generalize this behavior; send a non-empty ReplaceAll list for all clusters in a later Matter version and
     242              :         // enforce all clusters to support it in testing and in certification.
     243          875 :         bool encodeEmptyListAsReplaceAll = !(path.mClusterId == Clusters::AccessControl::Id);
     244              : 
     245          875 :         ReturnErrorOnFailure(EnsureMessage());
     246              : 
     247          875 :         if (encodeEmptyListAsReplaceAll)
     248              :         {
     249          466 :             ReturnErrorOnFailure(EncodeSingleAttributeDataIB(path, DataModel::List<uint8_t>()));
     250              :         }
     251              :         else
     252              :         {
     253              :             // Encode as many list-items as possible into a single AttributeDataIB, which will be included in a single
     254              :             // WriteRequestMessage chunk.
     255          409 :             ReturnErrorOnFailure(TryEncodeListIntoSingleAttributeDataIB(path, listValue, chunkingNeeded, encodedItemCount));
     256              : 
     257              :             // If all list items fit perfectly into a single AttributeDataIB, there is no need for any `append-item` or chunking,
     258              :             // and we can exit early.
     259          409 :             VerifyOrReturnError(chunkingNeeded, CHIP_NO_ERROR);
     260              : 
     261              :             // Start a new WriteRequest chunk, as there are still remaining list items to encode. These remaining items will be
     262              :             // appended one by one, each into its own AttributeDataIB. Unlike the first chunk (which contains only one
     263              :             // AttributeDataIB), subsequent chunks may contain multiple AttributeDataIBs if space allows it.
     264          391 :             ReturnErrorOnFailure(StartNewMessage());
     265          391 :             firstItemToAppendIndex = encodedItemCount;
     266              :         }
     267              : 
     268          857 :         path.mListOp = ConcreteDataAttributePath::ListOperation::AppendItem;
     269              : 
     270         4301 :         for (ListIndex i = firstItemToAppendIndex; i < listValue.size(); i++)
     271              :         {
     272         3545 :             ReturnErrorOnFailure(EncodeSingleAttributeDataIB(path, listValue[i]));
     273              :         }
     274              : 
     275          756 :         return CHIP_NO_ERROR;
     276              :     }
     277              : 
     278              :     /**
     279              :      * Encode a Nullable attribute value.  This needs a separate overload so it can dispatch to the right
     280              :      * EncodeAttribute when writing a nullable list.
     281              :      */
     282              :     template <class T>
     283          128 :     CHIP_ERROR EncodeAttribute(const AttributePathParams & attributePath, const DataModel::Nullable<T> & value,
     284              :                                const Optional<DataVersion> & aDataVersion = NullOptional)
     285              :     {
     286          128 :         ReturnErrorOnFailure(EnsureMessage());
     287              : 
     288          128 :         if (value.IsNull())
     289              :         {
     290              :             // Here, we are using kInvalidEndpointId to for missing endpoint id, which is used when sending group write requests.
     291          127 :             return EncodeSingleAttributeDataIB(
     292          254 :                 ConcreteDataAttributePath(attributePath.HasWildcardEndpointId() ? kInvalidEndpointId : attributePath.mEndpointId,
     293          127 :                                           attributePath.mClusterId, attributePath.mAttributeId, aDataVersion),
     294          127 :                 value);
     295              :         }
     296              : 
     297            1 :         return EncodeAttribute(attributePath, value.Value(), aDataVersion);
     298              :     }
     299              : 
     300              :     enum class TestListEncodingOverride
     301              :     {
     302              :         kNoOverride,
     303              :         kForceLegacyEncoding
     304              :     };
     305              : 
     306              :     /**
     307              :      * Encode an attribute value which is already encoded into a TLV. The TLVReader is expected to be initialized and the read head
     308              :      * is expected to point to the element to be encoded.
     309              :      *
     310              :      * Note: When encoding lists with this function, you may receive more than one write status for a single list. You can refer
     311              :      * to ChunkedWriteCallback.h for a high level API which will merge status codes for chunked write requests.
     312              :      *
     313              :      * Note: forceLegacyListEncoding is used by Test Harness and Python Tests to test backward compatibility and ensure end devices
     314              :      * support legacy WriteClients
     315              :      */
     316              :     CHIP_ERROR PutPreencodedAttribute(const ConcreteDataAttributePath & attributePath, const TLV::TLVReader & data,
     317              :                                       TestListEncodingOverride encodingBehavior = TestListEncodingOverride::kNoOverride);
     318              : 
     319              :     /**
     320              :      *  Once SendWriteRequest returns successfully, the WriteClient will
     321              :      *  handle calling Shutdown on itself once it decides it's done with waiting
     322              :      *  for a response (i.e. times out or gets a response). Client can specify
     323              :      *  the maximum time to wait for response (in milliseconds) via timeout parameter.
     324              :      *  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.
     325              :      *  If SendWriteRequest is never called, or the call fails, the API
     326              :      *  consumer is responsible for calling Shutdown on the WriteClient.
     327              :      */
     328              :     CHIP_ERROR SendWriteRequest(const SessionHandle & session, System::Clock::Timeout timeout = System::Clock::kZero);
     329              : 
     330              :     /**
     331              :      *  Returns true if the WriteRequest is Chunked.
     332              :      *  WARNING: This method is only used for UnitTests. It should only be called AFTER a call
     333              :      * EncodeAttribute/PutPreencodedAttribute AND BEFORE a call to SendWriteRequest(); only during this window does
     334              :      * "!mChunks.IsNull()" reliably indicate that the WriteRequest is chunked.
     335              :      */
     336          303 :     bool IsWriteRequestChunked() const { return !mChunks.IsNull(); };
     337              : 
     338              : private:
     339              :     friend class TestWriteInteraction;
     340              :     friend class InteractionModelEngine;
     341              :     enum class State
     342              :     {
     343              :         Initialized = 0,     // The client has been initialized
     344              :         AddAttribute,        // The client has added attribute and ready for a SendWriteRequest
     345              :         AwaitingTimedStatus, // Sent a Tiemd Request, waiting for response.
     346              :         AwaitingResponse,    // The client has sent out the write request message
     347              :         ResponseReceived,    // We have gotten a response after sending write request
     348              :         AwaitingDestruction, // The object has completed its work and is awaiting destruction by the application.
     349              :     };
     350              : 
     351              :     CHIP_ERROR OnMessageReceived(Messaging::ExchangeContext * apExchangeContext, const PayloadHeader & aPayloadHeader,
     352              :                                  System::PacketBufferHandle && aPayload) override;
     353              :     void OnResponseTimeout(Messaging::ExchangeContext * apExchangeContext) override;
     354              : 
     355              :     void MoveToState(const State aTargetState);
     356              :     CHIP_ERROR ProcessWriteResponseMessage(System::PacketBufferHandle && payload);
     357              :     CHIP_ERROR ProcessAttributeStatusIB(AttributeStatusIB::Parser & aAttributeStatusIB);
     358              :     const char * GetStateStr() const;
     359              : 
     360              :     // TODO (#38453) Clarify and fix the API contract of EnsureListStarted, TryToStartList and EnsureListEnded; in the case of
     361              :     // encoding failure, should we just undo buffer reservation? rollback to a checkpoint that we create within EnsureListStarted?
     362              :     // or just leave the WriteClient in a bad state.
     363              :     /**
     364              :      *     A wrapper for TryToStartList which will start a new chunk when TryToStartList fails with CHIP_ERROR_NO_MEMORY or
     365              :      * CHIP_ERROR_BUFFER_TOO_SMALL.
     366              :      *
     367              :      * @note Must always be followed by a call to EnsureListEnded(), to undo buffer reservation that took place within
     368              :      * it, and properly close TLV Containers.
     369              :      */
     370              :     CHIP_ERROR EnsureListStarted(const ConcreteDataAttributePath & attributePath);
     371              : 
     372              :     /**
     373              :      * Prepare the Encoding of an Attribute with List DataType into an AttributeDataIB.
     374              :      *
     375              :      */
     376              :     CHIP_ERROR TryToStartList(const ConcreteDataAttributePath & attributePath);
     377              : 
     378              :     /**
     379              :      * Complete the Encoding of an Attribute with List DataType into an AttributeDataIB.
     380              :      *
     381              :      * @note Must always be called after EnsureListStarted(), even in cases of encoding failures; to undo buffer reservation that
     382              :      * took place in EnsureListStarted.
     383              :      */
     384              :     CHIP_ERROR EnsureListEnded();
     385              : 
     386              :     /**
     387              :      *  Encode an attribute value that can be directly encoded using DataModel::Encode.
     388              :      */
     389              :     template <class T, std::enable_if_t<!DataModel::IsFabricScoped<T>::value, int> = 0>
     390         6727 :     CHIP_ERROR TryEncodeSingleAttributeDataIB(const ConcreteDataAttributePath & attributePath, const T & value)
     391              :     {
     392         6727 :         chip::TLV::TLVWriter * writer = nullptr;
     393              : 
     394         6727 :         ReturnErrorOnFailure(PrepareAttributeIB(attributePath));
     395         6378 :         VerifyOrReturnError((writer = GetAttributeDataIBTLVWriter()) != nullptr, CHIP_ERROR_INCORRECT_STATE);
     396         6378 :         ReturnErrorOnFailure(DataModel::Encode(*writer, chip::TLV::ContextTag(chip::app::AttributeDataIB::Tag::kData), value));
     397         4181 :         ReturnErrorOnFailure(FinishAttributeIB());
     398              : 
     399         4163 :         return CHIP_NO_ERROR;
     400              :     }
     401              : 
     402              :     template <class T, std::enable_if_t<DataModel::IsFabricScoped<T>::value, int> = 0>
     403            4 :     CHIP_ERROR TryEncodeSingleAttributeDataIB(const ConcreteDataAttributePath & attributePath, const T & value)
     404              :     {
     405            4 :         chip::TLV::TLVWriter * writer = nullptr;
     406              : 
     407            4 :         ReturnErrorOnFailure(PrepareAttributeIB(attributePath));
     408            4 :         VerifyOrReturnError((writer = GetAttributeDataIBTLVWriter()) != nullptr, CHIP_ERROR_INCORRECT_STATE);
     409            4 :         ReturnErrorOnFailure(
     410              :             DataModel::EncodeForWrite(*writer, chip::TLV::ContextTag(chip::app::AttributeDataIB::Tag::kData), value));
     411            4 :         ReturnErrorOnFailure(FinishAttributeIB());
     412              : 
     413            4 :         return CHIP_NO_ERROR;
     414              :     }
     415              : 
     416              :     template <class T>
     417          409 :     CHIP_ERROR TryEncodeListIntoSingleAttributeDataIB(const ConcreteDataAttributePath & attributePath,
     418              :                                                       const DataModel::List<T> & list, bool & outChunkingNeeded,
     419              :                                                       uint16_t & outEncodedItemCount)
     420              :     {
     421          409 :         ReturnErrorOnFailure(EnsureListStarted(attributePath));
     422              : 
     423          409 :         AttributeDataIB::Builder & attributeDataIB = mWriteRequestBuilder.GetWriteRequests().GetAttributeDataIBBuilder();
     424          409 :         TLV::TLVWriter backupWriter;
     425          409 :         outEncodedItemCount = 0;
     426              : 
     427         1565 :         for (auto & item : list)
     428              :         {
     429              :             // Try to put all the list items into the list we just started, until we either run out of items
     430              :             // or run out of space.
     431              :             // Make sure that if we run out of space we don't leave a partially-encoded list item around.
     432         1547 :             attributeDataIB.Checkpoint(backupWriter);
     433         1547 :             CHIP_ERROR err = CHIP_NO_ERROR;
     434              : 
     435              :             if constexpr (DataModel::IsFabricScoped<T>::value)
     436              :             {
     437            0 :                 err = DataModel::EncodeForWrite(*attributeDataIB.GetWriter(), TLV::AnonymousTag(), item);
     438              :             }
     439              :             else
     440              :             {
     441         1547 :                 err = DataModel::Encode(*attributeDataIB.GetWriter(), TLV::AnonymousTag(), item);
     442              :             }
     443              : 
     444         4250 :             if (err == CHIP_ERROR_NO_MEMORY || err == CHIP_ERROR_BUFFER_TOO_SMALL)
     445              :             {
     446              :                 // Rollback through the attributeDataIB, which also resets the Builder's error state.
     447              :                 // This returns the object to the state it was in before attempting to encode the list item.
     448          391 :                 attributeDataIB.Rollback(backupWriter);
     449          391 :                 outChunkingNeeded = true;
     450          391 :                 break;
     451              :             }
     452         1156 :             ReturnErrorOnFailure(err);
     453         1156 :             outEncodedItemCount++;
     454              :         }
     455              : 
     456          409 :         return EnsureListEnded();
     457              :     }
     458              : 
     459              :     /**
     460              :      * A wrapper for TryEncodeSingleAttributeDataIB which will start a new chunk when failed with CHIP_ERROR_NO_MEMORY or
     461              :      * CHIP_ERROR_BUFFER_TOO_SMALL.
     462              :      *
     463              :      * NOTE: This method must not be used for encoding non-empty lists, even if the template accepts a list type.
     464              :      * For such cases, use TryEncodeListIntoSingleAttributeDataIB as part of a suitable encoding strategy,
     465              :      * since it has a different contract and has different usage expectations.
     466              :      */
     467              :     template <class T>
     468         4268 :     CHIP_ERROR EncodeSingleAttributeDataIB(const ConcreteDataAttributePath & attributePath, const T & value)
     469              :     {
     470         4268 :         TLV::TLVWriter backupWriter;
     471              : 
     472         4268 :         mWriteRequestBuilder.GetWriteRequests().Checkpoint(backupWriter);
     473              : 
     474              :         // First attempt to write this attribute.
     475         4268 :         CHIP_ERROR err = TryEncodeSingleAttributeDataIB(attributePath, value);
     476        10341 :         if (err == CHIP_ERROR_NO_MEMORY || err == CHIP_ERROR_BUFFER_TOO_SMALL)
     477              :         {
     478              :             // If it failed with no memory, then we create a new chunk for it.
     479         2463 :             mWriteRequestBuilder.GetWriteRequests().Rollback(backupWriter);
     480         2463 :             ReturnErrorOnFailure(StartNewMessage());
     481         2463 :             ReturnErrorOnFailure(TryEncodeSingleAttributeDataIB(attributePath, value));
     482              :         }
     483              :         else
     484              :         {
     485         1805 :             ReturnErrorOnFailure(err);
     486              :         }
     487              : 
     488         4167 :         return CHIP_NO_ERROR;
     489              :     }
     490              : 
     491              :     /**
     492              :      * Encode a preencoded attribute data, returns TLV encode error if the remaining space of current chunk is too small for the
     493              :      * AttributeDataIB.
     494              :      */
     495              :     CHIP_ERROR TryPutSinglePreencodedAttributeWritePayload(const ConcreteDataAttributePath & attributePath,
     496              :                                                            const TLV::TLVReader & data);
     497              : 
     498              :     /**
     499              :      * Encode a preencoded attribute data, will try to create a new chunk when necessary.
     500              :      */
     501              :     CHIP_ERROR PutSinglePreencodedAttributeWritePayload(const ConcreteDataAttributePath & attributePath,
     502              :                                                         const TLV::TLVReader & data);
     503              : 
     504              :     /**
     505              :      * Encodes preencoded attribute data into a list, that will be decoded by cluster servers as a REPLACE Change.
     506              :      * Returns outChunkingNeeded = true if it was not possible to fit all the data into a single list.
     507              :      */
     508              :     CHIP_ERROR TryPutPreencodedAttributeWritePayloadIntoList(const chip::app::ConcreteDataAttributePath & attributePath,
     509              :                                                              TLV::TLVReader & valueReader, bool & outChunkingNeeded,
     510              :                                                              uint16_t & outEncodedItemCount);
     511              :     CHIP_ERROR EnsureMessage();
     512              : 
     513              :     /**
     514              :      * Called internally to signal the completion of all work on this object, gracefully close the
     515              :      * exchange (by calling into the base class) and finally, signal to the application that it's
     516              :      * safe to release this object.
     517              :      */
     518              :     void Close();
     519              : 
     520              :     /**
     521              :      * This forcibly closes the exchange context if a valid one is pointed to. Such a situation does
     522              :      * not arise during normal message processing flows that all normally call Close() above. This can only
     523              :      * arise due to application-initiated destruction of the object when this object is handling receiving/sending
     524              :      * message payloads.
     525              :      */
     526              :     void Abort();
     527              : 
     528              :     // Send our queued-up Write Request message.  Assumes the exchange is ready
     529              :     // and mPendingWriteData is populated.
     530              :     CHIP_ERROR SendWriteRequest();
     531              : 
     532              :     // Encodes the header of an AttributeDataIB, a special case for attributePath is its EndpointId can be kInvalidEndpointId, this
     533              :     // is used when sending group write requests.
     534              :     // TODO(#14935) Update AttributePathParams to support more list operations.
     535              :     CHIP_ERROR PrepareAttributeIB(const ConcreteDataAttributePath & attributePath);
     536              :     CHIP_ERROR FinishAttributeIB();
     537              :     TLV::TLVWriter * GetAttributeDataIBTLVWriter();
     538              : 
     539              :     /**
     540              :      * Create a new message (or a new chunk) for the write request.
     541              :      */
     542              :     CHIP_ERROR StartNewMessage();
     543              : 
     544              :     /**
     545              :      * Finalize Write Request Message TLV Builder and retrieve final data from tlv builder for later sending
     546              :      */
     547              :     CHIP_ERROR FinalizeMessage(bool aHasMoreChunks);
     548              : 
     549              :     Messaging::ExchangeManager * mpExchangeMgr = nullptr;
     550              :     Messaging::ExchangeHolder mExchangeCtx;
     551              :     Callback * mpCallback = nullptr;
     552              :     State mState          = State::Initialized;
     553              :     System::PacketBufferTLVWriter mMessageWriter;
     554              :     WriteRequestMessage::Builder mWriteRequestBuilder;
     555              :     // TODO Maybe we should change PacketBufferTLVWriter so we can finalize it
     556              :     // but have it hold on to the buffer, and get the buffer from it later.
     557              :     // Then we could avoid this extra pointer-sized member.
     558              :     System::PacketBufferHandle mPendingWriteData;
     559              :     // If mTimedWriteTimeoutMs has a value, we are expected to do a timed
     560              :     // write.
     561              :     Optional<uint16_t> mTimedWriteTimeoutMs;
     562              :     bool mSuppressResponse = false;
     563              : 
     564              :     // A list of buffers, one buffer for each chunk.
     565              :     System::PacketBufferHandle mChunks;
     566              : 
     567              :     // TODO: This file might be compiled with different build flags on Darwin platform (when building WriteClient.cpp and
     568              :     // CHIPClustersObjc.mm), which will cause undefined behavior when building write requests. Uncomment the #if and #endif after
     569              :     // resolving it.
     570              :     // #if CONFIG_BUILD_FOR_HOST_UNIT_TEST
     571              :     uint16_t mReservedSize = 0;
     572              :     // #endif
     573              : 
     574              :     /**
     575              :      * The value of the TimedRequest field in the WriteRequest message.
     576              :      *
     577              :      * This tells the server whether this write was preceded by a Timed Request action.
     578              :      * Normally this matches whether mTimedWriteTimeoutMs has a value, but test constructors
     579              :      * can decouple these to test protocol mismatch scenarios.
     580              :      */
     581              :     bool mTimedRequestFieldValue = false;
     582              : 
     583              :     /**
     584              :      * Below we define several const variables for encoding overheads.
     585              :      * WriteRequestMessage =
     586              :      * {
     587              :      *  timedRequest = false,
     588              :      *  AttributeDataIBs =
     589              :      *  [
     590              :      *     AttributeDataIB =             \
     591              :      *     {                              |
     592              :      *        DataVersion = 0x0,          |
     593              :      *        AttributePathIB =           |
     594              :      *        {                           |
     595              :      *           Endpoint = 0x2,          |  "atomically" encoded via
     596              :      *           Cluster = 0x50f,          > EncodeAttribute or
     597              :      *           Attribute = 0x0000_0006, |  PutPreencodedAttribute
     598              :      *           ListIndex = Null,        |
     599              :      *        }                           |
     600              :      *        Data = ...                  |
     601              :      *     },                             /
     602              :      *     (...)
     603              :      *  ],                           <-- 1 byte  "end of AttributeDataIB" (end of container)
     604              :      *  moreChunkedMessages = false, <-- 2 bytes "kReservedSizeForMoreChunksFlag"
     605              :      *  InteractionModelRevision = 1,<-- 3 bytes "kReservedSizeForIMRevision"
     606              :      * }                             <-- 1 byte  "end of WriteRequestMessage" (end of container)
     607              :      */
     608              : 
     609              :     // Reserved size for the MoreChunks boolean flag, which takes up 1 byte for the control tag and 1 byte for the context tag.
     610              :     static constexpr uint16_t kReservedSizeForMoreChunksFlag = 1 + 1;
     611              :     // End Of Container (0x18) uses one byte.
     612              :     static constexpr uint16_t kReservedSizeForEndOfContainer = 1;
     613              :     // Reserved size for the uint8_t InteractionModelRevision flag, which takes up 1 byte for the control tag and 1 byte for the
     614              :     // context tag, 1 byte for value
     615              :     static constexpr uint16_t kReservedSizeForIMRevision = 1 + 1 + 1;
     616              :     // Reserved buffer for TLV level overhead (the overhead for end of AttributeDataIBs (end of container), more chunks flag, end
     617              :     // of WriteRequestMessage (another end of container)).
     618              :     static constexpr uint16_t kReservedSizeForTLVEncodingOverhead = kReservedSizeForIMRevision + kReservedSizeForMoreChunksFlag +
     619              :         kReservedSizeForEndOfContainer + kReservedSizeForEndOfContainer;
     620              :     bool mHasDataVersion = false;
     621              : 
     622              :     static constexpr uint16_t kReservedSizeForEndOfListAttributeIB =
     623              :         kReservedSizeForEndOfContainer + AttributeDataIB::Builder::GetSizeToEndAttributeDataIB();
     624              : 
     625              :     static constexpr TLV::TLVType kAttributeDataIBType = TLV::kTLVType_Structure;
     626              : };
     627              : 
     628              : } // namespace app
     629              : } // namespace chip
        

Generated by: LCOV version 2.0-1