Matter SDK Coverage Report
Current view: top level - app - WriteClient.h (source / functions) Coverage Total Hit
Test: SHA:209dc18e4021e7d0dff8120ccc585909391dd862 Lines: 95.8 % 95 91
Test Date: 2026-06-16 07:34:53 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         1072 :         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          718 :     WriteClient(Messaging::ExchangeManager * apExchangeMgr, Callback * apCallback, const Optional<uint16_t> & aTimedWriteTimeoutMs,
     129          718 :                 bool aSuppressResponse = false) :
     130          718 :         mpExchangeMgr(apExchangeMgr),
     131          718 :         mExchangeCtx(*this), mpCallback(apCallback), mTimedWriteTimeoutMs(aTimedWriteTimeoutMs),
     132         1436 :         mSuppressResponse(aSuppressResponse), mTimedRequestFieldValue(aTimedWriteTimeoutMs.HasValue())
     133              :     {
     134          718 :         assertChipStackLockedByCurrentThread();
     135          718 :     }
     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         1120 :     ~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          857 :         const size_t listSize = listValue.size();
     271         4301 :         for (size_t i = firstItemToAppendIndex; i < listSize; i++)
     272              :         {
     273         3545 :             ReturnErrorOnFailure(EncodeSingleAttributeDataIB(path, listValue[i]));
     274              :         }
     275              : 
     276          756 :         return CHIP_NO_ERROR;
     277              :     }
     278              : 
     279              :     /**
     280              :      * Encode a Nullable attribute value.  This needs a separate overload so it can dispatch to the right
     281              :      * EncodeAttribute when writing a nullable list.
     282              :      */
     283              :     template <class T>
     284          128 :     CHIP_ERROR EncodeAttribute(const AttributePathParams & attributePath, const DataModel::Nullable<T> & value,
     285              :                                const Optional<DataVersion> & aDataVersion = NullOptional)
     286              :     {
     287          128 :         ReturnErrorOnFailure(EnsureMessage());
     288              : 
     289          128 :         if (value.IsNull())
     290              :         {
     291              :             // Here, we are using kInvalidEndpointId to for missing endpoint id, which is used when sending group write requests.
     292          127 :             return EncodeSingleAttributeDataIB(
     293          254 :                 ConcreteDataAttributePath(attributePath.HasWildcardEndpointId() ? kInvalidEndpointId : attributePath.mEndpointId,
     294          127 :                                           attributePath.mClusterId, attributePath.mAttributeId, aDataVersion),
     295          127 :                 value);
     296              :         }
     297              : 
     298            1 :         return EncodeAttribute(attributePath, value.Value(), aDataVersion);
     299              :     }
     300              : 
     301              :     enum class TestListEncodingOverride
     302              :     {
     303              :         kNoOverride,
     304              :         kForceLegacyEncoding
     305              :     };
     306              : 
     307              :     /**
     308              :      * Encode an attribute value which is already encoded into a TLV. The TLVReader is expected to be initialized and the read head
     309              :      * is expected to point to the element to be encoded.
     310              :      *
     311              :      * Note: When encoding lists with this function, you may receive more than one write status for a single list. You can refer
     312              :      * to ChunkedWriteCallback.h for a high level API which will merge status codes for chunked write requests.
     313              :      *
     314              :      * Note: forceLegacyListEncoding is used by Test Harness and Python Tests to test backward compatibility and ensure end devices
     315              :      * support legacy WriteClients
     316              :      */
     317              :     CHIP_ERROR PutPreencodedAttribute(const ConcreteDataAttributePath & attributePath, const TLV::TLVReader & data,
     318              :                                       TestListEncodingOverride encodingBehavior = TestListEncodingOverride::kNoOverride);
     319              : 
     320              :     /**
     321              :      *  Once SendWriteRequest returns successfully, the WriteClient will
     322              :      *  handle calling Shutdown on itself once it decides it's done with waiting
     323              :      *  for a response (i.e. times out or gets a response). Client can specify
     324              :      *  the maximum time to wait for response (in milliseconds) via timeout parameter.
     325              :      *  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.
     326              :      *  If SendWriteRequest is never called, or the call fails, the API
     327              :      *  consumer is responsible for calling Shutdown on the WriteClient.
     328              :      */
     329              :     CHIP_ERROR SendWriteRequest(const SessionHandle & session, System::Clock::Timeout timeout = System::Clock::kZero);
     330              : 
     331              :     /**
     332              :      *  Returns true if the WriteRequest is Chunked.
     333              :      *  WARNING: This method is only used for UnitTests. It should only be called AFTER a call
     334              :      * EncodeAttribute/PutPreencodedAttribute AND BEFORE a call to SendWriteRequest(); only during this window does
     335              :      * "!mChunks.IsNull()" reliably indicate that the WriteRequest is chunked.
     336              :      */
     337          303 :     bool IsWriteRequestChunked() const { return !mChunks.IsNull(); };
     338              : 
     339              : private:
     340              :     friend class TestWriteInteraction;
     341              :     friend class InteractionModelEngine;
     342              :     enum class State
     343              :     {
     344              :         Initialized = 0,     // The client has been initialized
     345              :         AddAttribute,        // The client has added attribute and ready for a SendWriteRequest
     346              :         AwaitingTimedStatus, // Sent a Tiemd Request, waiting for response.
     347              :         AwaitingResponse,    // The client has sent out the write request message
     348              :         ResponseReceived,    // We have gotten a response after sending write request
     349              :         AwaitingDestruction, // The object has completed its work and is awaiting destruction by the application.
     350              :     };
     351              : 
     352              :     CHIP_ERROR OnMessageReceived(Messaging::ExchangeContext * apExchangeContext, const PayloadHeader & aPayloadHeader,
     353              :                                  System::PacketBufferHandle && aPayload) override;
     354              :     void OnResponseTimeout(Messaging::ExchangeContext * apExchangeContext) override;
     355              : 
     356              :     void MoveToState(const State aTargetState);
     357              :     CHIP_ERROR ProcessWriteResponseMessage(System::PacketBufferHandle && payload);
     358              :     CHIP_ERROR ProcessAttributeStatusIB(AttributeStatusIB::Parser & aAttributeStatusIB);
     359              :     const char * GetStateStr() const;
     360              : 
     361              :     // TODO (#38453) Clarify and fix the API contract of EnsureListStarted, TryToStartList and EnsureListEnded; in the case of
     362              :     // encoding failure, should we just undo buffer reservation? rollback to a checkpoint that we create within EnsureListStarted?
     363              :     // or just leave the WriteClient in a bad state.
     364              :     /**
     365              :      *     A wrapper for TryToStartList which will start a new chunk when TryToStartList fails with CHIP_ERROR_NO_MEMORY or
     366              :      * CHIP_ERROR_BUFFER_TOO_SMALL.
     367              :      *
     368              :      * @note Must always be followed by a call to EnsureListEnded(), to undo buffer reservation that took place within
     369              :      * it, and properly close TLV Containers.
     370              :      */
     371              :     CHIP_ERROR EnsureListStarted(const ConcreteDataAttributePath & attributePath);
     372              : 
     373              :     /**
     374              :      * Prepare the Encoding of an Attribute with List DataType into an AttributeDataIB.
     375              :      *
     376              :      */
     377              :     CHIP_ERROR TryToStartList(const ConcreteDataAttributePath & attributePath);
     378              : 
     379              :     /**
     380              :      * Complete the Encoding of an Attribute with List DataType into an AttributeDataIB.
     381              :      *
     382              :      * @note Must always be called after EnsureListStarted(), even in cases of encoding failures; to undo buffer reservation that
     383              :      * took place in EnsureListStarted.
     384              :      */
     385              :     CHIP_ERROR EnsureListEnded();
     386              : 
     387              :     /**
     388              :      *  Encode an attribute value that can be directly encoded using DataModel::Encode.
     389              :      */
     390              :     template <class T, std::enable_if_t<!DataModel::IsFabricScoped<T>::value, int> = 0>
     391         6728 :     CHIP_ERROR TryEncodeSingleAttributeDataIB(const ConcreteDataAttributePath & attributePath, const T & value)
     392              :     {
     393         6728 :         chip::TLV::TLVWriter * writer = nullptr;
     394              : 
     395         6728 :         ReturnErrorOnFailure(PrepareAttributeIB(attributePath));
     396         6379 :         VerifyOrReturnError((writer = GetAttributeDataIBTLVWriter()) != nullptr, CHIP_ERROR_INCORRECT_STATE);
     397         6379 :         ReturnErrorOnFailure(DataModel::Encode(*writer, chip::TLV::ContextTag(chip::app::AttributeDataIB::Tag::kData), value));
     398         4182 :         ReturnErrorOnFailure(FinishAttributeIB());
     399              : 
     400         4164 :         return CHIP_NO_ERROR;
     401              :     }
     402              : 
     403              :     template <class T, std::enable_if_t<DataModel::IsFabricScoped<T>::value, int> = 0>
     404            4 :     CHIP_ERROR TryEncodeSingleAttributeDataIB(const ConcreteDataAttributePath & attributePath, const T & value)
     405              :     {
     406            4 :         chip::TLV::TLVWriter * writer = nullptr;
     407              : 
     408            4 :         ReturnErrorOnFailure(PrepareAttributeIB(attributePath));
     409            4 :         VerifyOrReturnError((writer = GetAttributeDataIBTLVWriter()) != nullptr, CHIP_ERROR_INCORRECT_STATE);
     410            4 :         ReturnErrorOnFailure(
     411              :             DataModel::EncodeForWrite(*writer, chip::TLV::ContextTag(chip::app::AttributeDataIB::Tag::kData), value));
     412            4 :         ReturnErrorOnFailure(FinishAttributeIB());
     413              : 
     414            4 :         return CHIP_NO_ERROR;
     415              :     }
     416              : 
     417              :     template <class T>
     418          409 :     CHIP_ERROR TryEncodeListIntoSingleAttributeDataIB(const ConcreteDataAttributePath & attributePath,
     419              :                                                       const DataModel::List<T> & list, bool & outChunkingNeeded,
     420              :                                                       uint16_t & outEncodedItemCount)
     421              :     {
     422          409 :         ReturnErrorOnFailure(EnsureListStarted(attributePath));
     423              : 
     424          409 :         AttributeDataIB::Builder & attributeDataIB = mWriteRequestBuilder.GetWriteRequests().GetAttributeDataIBBuilder();
     425          409 :         TLV::TLVWriter backupWriter;
     426          409 :         outEncodedItemCount = 0;
     427              : 
     428         1565 :         for (auto & item : list)
     429              :         {
     430              :             // Try to put all the list items into the list we just started, until we either run out of items
     431              :             // or run out of space.
     432              :             // Make sure that if we run out of space we don't leave a partially-encoded list item around.
     433         1547 :             attributeDataIB.Checkpoint(backupWriter);
     434         1547 :             CHIP_ERROR err = CHIP_NO_ERROR;
     435              : 
     436              :             if constexpr (DataModel::IsFabricScoped<T>::value)
     437              :             {
     438            0 :                 err = DataModel::EncodeForWrite(*attributeDataIB.GetWriter(), TLV::AnonymousTag(), item);
     439              :             }
     440              :             else
     441              :             {
     442         1547 :                 err = DataModel::Encode(*attributeDataIB.GetWriter(), TLV::AnonymousTag(), item);
     443              :             }
     444              : 
     445         4250 :             if (err == CHIP_ERROR_NO_MEMORY || err == CHIP_ERROR_BUFFER_TOO_SMALL)
     446              :             {
     447              :                 // Rollback through the attributeDataIB, which also resets the Builder's error state.
     448              :                 // This returns the object to the state it was in before attempting to encode the list item.
     449          391 :                 attributeDataIB.Rollback(backupWriter);
     450          391 :                 outChunkingNeeded = true;
     451          391 :                 break;
     452              :             }
     453         1156 :             ReturnErrorOnFailure(err);
     454         1156 :             outEncodedItemCount++;
     455              :         }
     456              : 
     457          409 :         return EnsureListEnded();
     458              :     }
     459              : 
     460              :     /**
     461              :      * A wrapper for TryEncodeSingleAttributeDataIB which will start a new chunk when failed with CHIP_ERROR_NO_MEMORY or
     462              :      * CHIP_ERROR_BUFFER_TOO_SMALL.
     463              :      *
     464              :      * NOTE: This method must not be used for encoding non-empty lists, even if the template accepts a list type.
     465              :      * For such cases, use TryEncodeListIntoSingleAttributeDataIB as part of a suitable encoding strategy,
     466              :      * since it has a different contract and has different usage expectations.
     467              :      */
     468              :     template <class T>
     469         4269 :     CHIP_ERROR EncodeSingleAttributeDataIB(const ConcreteDataAttributePath & attributePath, const T & value)
     470              :     {
     471         4269 :         TLV::TLVWriter backupWriter;
     472              : 
     473         4269 :         mWriteRequestBuilder.GetWriteRequests().Checkpoint(backupWriter);
     474              : 
     475              :         // First attempt to write this attribute.
     476         4269 :         CHIP_ERROR err = TryEncodeSingleAttributeDataIB(attributePath, value);
     477        10344 :         if (err == CHIP_ERROR_NO_MEMORY || err == CHIP_ERROR_BUFFER_TOO_SMALL)
     478              :         {
     479              :             // If it failed with no memory, then we create a new chunk for it.
     480         2463 :             mWriteRequestBuilder.GetWriteRequests().Rollback(backupWriter);
     481         2463 :             ReturnErrorOnFailure(StartNewMessage());
     482         2463 :             ReturnErrorOnFailure(TryEncodeSingleAttributeDataIB(attributePath, value));
     483              :         }
     484              :         else
     485              :         {
     486         1806 :             ReturnErrorOnFailure(err);
     487              :         }
     488              : 
     489         4168 :         return CHIP_NO_ERROR;
     490              :     }
     491              : 
     492              :     /**
     493              :      * Encode a preencoded attribute data, returns TLV encode error if the remaining space of current chunk is too small for the
     494              :      * AttributeDataIB.
     495              :      */
     496              :     CHIP_ERROR TryPutSinglePreencodedAttributeWritePayload(const ConcreteDataAttributePath & attributePath,
     497              :                                                            const TLV::TLVReader & data);
     498              : 
     499              :     /**
     500              :      * Encode a preencoded attribute data, will try to create a new chunk when necessary.
     501              :      */
     502              :     CHIP_ERROR PutSinglePreencodedAttributeWritePayload(const ConcreteDataAttributePath & attributePath,
     503              :                                                         const TLV::TLVReader & data);
     504              : 
     505              :     /**
     506              :      * Encodes preencoded attribute data into a list, that will be decoded by cluster servers as a REPLACE Change.
     507              :      * Returns outChunkingNeeded = true if it was not possible to fit all the data into a single list.
     508              :      */
     509              :     CHIP_ERROR TryPutPreencodedAttributeWritePayloadIntoList(const chip::app::ConcreteDataAttributePath & attributePath,
     510              :                                                              TLV::TLVReader & valueReader, bool & outChunkingNeeded,
     511              :                                                              uint16_t & outEncodedItemCount);
     512              :     CHIP_ERROR EnsureMessage();
     513              : 
     514              :     /**
     515              :      * Called internally to signal the completion of all work on this object, gracefully close the
     516              :      * exchange (by calling into the base class) and finally, signal to the application that it's
     517              :      * safe to release this object.
     518              :      */
     519              :     void Close();
     520              : 
     521              :     /**
     522              :      * This forcibly closes the exchange context if a valid one is pointed to. Such a situation does
     523              :      * not arise during normal message processing flows that all normally call Close() above. This can only
     524              :      * arise due to application-initiated destruction of the object when this object is handling receiving/sending
     525              :      * message payloads.
     526              :      */
     527              :     void Abort();
     528              : 
     529              :     // Send our queued-up Write Request message.  Assumes the exchange is ready
     530              :     // and mPendingWriteData is populated.
     531              :     CHIP_ERROR SendWriteRequest();
     532              : 
     533              :     // Encodes the header of an AttributeDataIB, a special case for attributePath is its EndpointId can be kInvalidEndpointId, this
     534              :     // is used when sending group write requests.
     535              :     // TODO(#14935) Update AttributePathParams to support more list operations.
     536              :     CHIP_ERROR PrepareAttributeIB(const ConcreteDataAttributePath & attributePath);
     537              :     CHIP_ERROR FinishAttributeIB();
     538              :     TLV::TLVWriter * GetAttributeDataIBTLVWriter();
     539              : 
     540              :     /**
     541              :      * Create a new message (or a new chunk) for the write request.
     542              :      */
     543              :     CHIP_ERROR StartNewMessage();
     544              : 
     545              :     /**
     546              :      * Finalize Write Request Message TLV Builder and retrieve final data from tlv builder for later sending
     547              :      */
     548              :     CHIP_ERROR FinalizeMessage(bool aHasMoreChunks);
     549              : 
     550              :     Messaging::ExchangeManager * mpExchangeMgr = nullptr;
     551              :     Messaging::ExchangeHolder mExchangeCtx;
     552              :     Callback * mpCallback = nullptr;
     553              :     State mState          = State::Initialized;
     554              :     System::PacketBufferTLVWriter mMessageWriter;
     555              :     WriteRequestMessage::Builder mWriteRequestBuilder;
     556              :     // TODO Maybe we should change PacketBufferTLVWriter so we can finalize it
     557              :     // but have it hold on to the buffer, and get the buffer from it later.
     558              :     // Then we could avoid this extra pointer-sized member.
     559              :     System::PacketBufferHandle mPendingWriteData;
     560              :     // If mTimedWriteTimeoutMs has a value, we are expected to do a timed
     561              :     // write.
     562              :     Optional<uint16_t> mTimedWriteTimeoutMs;
     563              :     bool mSuppressResponse = false;
     564              : 
     565              :     // A list of buffers, one buffer for each chunk.
     566              :     System::PacketBufferHandle mChunks;
     567              : 
     568              :     // TODO: This file might be compiled with different build flags on Darwin platform (when building WriteClient.cpp and
     569              :     // CHIPClustersObjc.mm), which will cause undefined behavior when building write requests. Uncomment the #if and #endif after
     570              :     // resolving it.
     571              :     // #if CONFIG_BUILD_FOR_HOST_UNIT_TEST
     572              :     uint16_t mReservedSize = 0;
     573              :     // #endif
     574              : 
     575              :     /**
     576              :      * The value of the TimedRequest field in the WriteRequest message.
     577              :      *
     578              :      * This tells the server whether this write was preceded by a Timed Request action.
     579              :      * Normally this matches whether mTimedWriteTimeoutMs has a value, but test constructors
     580              :      * can decouple these to test protocol mismatch scenarios.
     581              :      */
     582              :     bool mTimedRequestFieldValue = false;
     583              : 
     584              :     /**
     585              :      * Below we define several const variables for encoding overheads.
     586              :      * WriteRequestMessage =
     587              :      * {
     588              :      *  timedRequest = false,
     589              :      *  AttributeDataIBs =
     590              :      *  [
     591              :      *     AttributeDataIB =             \
     592              :      *     {                              |
     593              :      *        DataVersion = 0x0,          |
     594              :      *        AttributePathIB =           |
     595              :      *        {                           |
     596              :      *           Endpoint = 0x2,          |  "atomically" encoded via
     597              :      *           Cluster = 0x50f,          > EncodeAttribute or
     598              :      *           Attribute = 0x0000_0006, |  PutPreencodedAttribute
     599              :      *           ListIndex = Null,        |
     600              :      *        }                           |
     601              :      *        Data = ...                  |
     602              :      *     },                             /
     603              :      *     (...)
     604              :      *  ],                           <-- 1 byte  "end of AttributeDataIB" (end of container)
     605              :      *  moreChunkedMessages = false, <-- 2 bytes "kReservedSizeForMoreChunksFlag"
     606              :      *  InteractionModelRevision = 1,<-- 3 bytes "kReservedSizeForIMRevision"
     607              :      * }                             <-- 1 byte  "end of WriteRequestMessage" (end of container)
     608              :      */
     609              : 
     610              :     // Reserved size for the MoreChunks boolean flag, which takes up 1 byte for the control tag and 1 byte for the context tag.
     611              :     static constexpr uint16_t kReservedSizeForMoreChunksFlag = 1 + 1;
     612              :     // End Of Container (0x18) uses one byte.
     613              :     static constexpr uint16_t kReservedSizeForEndOfContainer = 1;
     614              :     // Reserved size for the uint8_t InteractionModelRevision flag, which takes up 1 byte for the control tag and 1 byte for the
     615              :     // context tag, 1 byte for value
     616              :     static constexpr uint16_t kReservedSizeForIMRevision = 1 + 1 + 1;
     617              :     // Reserved buffer for TLV level overhead (the overhead for end of AttributeDataIBs (end of container), more chunks flag, end
     618              :     // of WriteRequestMessage (another end of container)).
     619              :     static constexpr uint16_t kReservedSizeForTLVEncodingOverhead = kReservedSizeForIMRevision + kReservedSizeForMoreChunksFlag +
     620              :         kReservedSizeForEndOfContainer + kReservedSizeForEndOfContainer;
     621              :     bool mHasDataVersion = false;
     622              : 
     623              :     static constexpr uint16_t kReservedSizeForEndOfListAttributeIB =
     624              :         kReservedSizeForEndOfContainer + AttributeDataIB::Builder::GetSizeToEndAttributeDataIB();
     625              : 
     626              :     static constexpr TLV::TLVType kAttributeDataIBType = TLV::kTLVType_Structure;
     627              : };
     628              : 
     629              : } // namespace app
     630              : } // namespace chip
        

Generated by: LCOV version 2.0-1