LCOV - code coverage report
Current view: top level - app - WriteClient.h (source / functions) Hit Total Coverage
Test: lcov_final.info Lines: 1 22 4.5 %
Date: 2024-02-15 08:20:41 Functions: 1 8 12.5 %

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

Generated by: LCOV version 1.14