Matter SDK Coverage Report
Current view: top level - app - AttributeValueEncoder.h (source / functions) Coverage Total Hit
Test: SHA:f84fe08d06f240e801b5d923f8a938a9938ca110 Lines: 100.0 % 34 34
Test Date: 2025-02-22 08:08:07 Functions: 100.0 % 12 12

            Line data    Source code
       1              : /*
       2              :  *    Copyright (c) 2021-2024 Project CHIP Authors
       3              :  *    All rights reserved.
       4              :  *
       5              :  *    Licensed under the Apache License, Version 2.0 (the "License");
       6              :  *    you may not use this file except in compliance with the License.
       7              :  *    You may obtain a copy of the License at
       8              :  *
       9              :  *        http://www.apache.org/licenses/LICENSE-2.0
      10              :  *
      11              :  *    Unless required by applicable law or agreed to in writing, software
      12              :  *    distributed under the License is distributed on an "AS IS" BASIS,
      13              :  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      14              :  *    See the License for the specific language governing permissions and
      15              :  *    limitations under the License.
      16              :  */
      17              : #pragma once
      18              : 
      19              : #include <access/SubjectDescriptor.h>
      20              : #include <app/AttributeEncodeState.h>
      21              : #include <app/AttributeReportBuilder.h>
      22              : #include <app/ConcreteAttributePath.h>
      23              : #include <app/MessageDef/AttributeReportIBs.h>
      24              : #include <app/data-model/FabricScoped.h>
      25              : #include <app/data-model/List.h>
      26              : 
      27              : #include <type_traits>
      28              : 
      29              : namespace chip {
      30              : namespace app {
      31              : 
      32              : /**
      33              :  * The AttributeValueEncoder is a helper class for filling report payloads into AttributeReportIBs.
      34              :  * The attribute value encoder can be initialized with a AttributeEncodeState for saving and recovering its state between encode
      35              :  * sessions (chunkings).
      36              :  *
      37              :  * When Encode returns recoverable errors (e.g. CHIP_ERROR_NO_MEMORY) the state can be used to initialize the AttributeValueEncoder
      38              :  * for future use on the same attribute path.
      39              :  */
      40              : class AttributeValueEncoder
      41              : {
      42              : public:
      43              :     class ListEncodeHelper
      44              :     {
      45              :     public:
      46            6 :         ListEncodeHelper(AttributeValueEncoder & encoder) : mAttributeValueEncoder(encoder) {}
      47              : 
      48              :         template <typename T, std::enable_if_t<DataModel::IsFabricScoped<T>::value, bool> = true>
      49              :         CHIP_ERROR Encode(const T & aArg) const
      50              :         {
      51              :             VerifyOrReturnError(aArg.GetFabricIndex() != kUndefinedFabricIndex, CHIP_ERROR_INVALID_FABRIC_INDEX);
      52              : 
      53              :             // If we are encoding for a fabric filtered attribute read and the fabric index does not match that present in the
      54              :             // request, skip encoding this list item.
      55              :             VerifyOrReturnError(!mAttributeValueEncoder.mIsFabricFiltered ||
      56              :                                     aArg.GetFabricIndex() == mAttributeValueEncoder.AccessingFabricIndex(),
      57              :                                 CHIP_NO_ERROR);
      58              :             return mAttributeValueEncoder.EncodeListItem(mCheckpoint, aArg, mAttributeValueEncoder.AccessingFabricIndex());
      59              :         }
      60              : 
      61              :         template <typename T, std::enable_if_t<!DataModel::IsFabricScoped<T>::value, bool> = true>
      62         1986 :         CHIP_ERROR Encode(const T & aArg) const
      63              :         {
      64         1986 :             return mAttributeValueEncoder.EncodeListItem(mCheckpoint, aArg);
      65              :         }
      66              : 
      67              :         // overrides that save flash: no need to care about the extra const
      68              :         // Without this, we have a usage of:
      69              :         //   chip::ChipError chip::app::AttributeValueEncoder::EncodeListItem<unsigned long const&>
      70              :         // Overall we tend to have very similar code expand (from nm):
      71              :         //    chip::ChipError chip::app::AttributeValueEncoder::Encode<unsigned char>(unsigned char&&)
      72              :         //    chip::ChipError chip::app::AttributeValueEncoder::Encode<unsigned char&>(unsigned char&)
      73              :         //    chip::ChipError chip::app::AttributeValueEncoder::Encode<unsigned char const&>(unsigned char const&)
      74              :         //    chip::ChipError chip::app::AttributeValueEncoder::Encode<unsigned short const&>(unsigned short const&)
      75              :         //    chip::ChipError chip::app::AttributeValueEncoder::Encode<unsigned long&>(unsigned long&)
      76              :         //    chip::ChipError chip::app::AttributeValueEncoder::Encode<unsigned short&>(unsigned short&)
      77              :         //    chip::ChipError chip::app::AttributeValueEncoder::Encode<unsigned long long&>(unsigned long long&)
      78              :         //    chip::ChipError chip::app::AttributeValueEncoder::Encode<unsigned short>(unsigned short&&)
      79              :         //    chip::ChipError chip::app::AttributeValueEncoder::Encode<unsigned long long>(unsigned long long&&)
      80              :         // that we try to reduce
      81              :         //
      82              :         // TODO:
      83              :         //   - we should figure where the extra const override is used
      84              :         //   - we should try to avoid having such footguns. This list template-explosion seems
      85              :         //     dangerous for flash.
      86              :         //
      87              :         // This relies on TLV numbers always being encoded as 64-bit value
      88              :         inline CHIP_ERROR Encode(uint32_t const & aArg) const { return Encode<uint64_t>(aArg); }
      89              :         inline CHIP_ERROR Encode(uint32_t & aArg) const { return Encode<uint64_t>(aArg); }
      90              :         inline CHIP_ERROR Encode(uint16_t const & aArg) const { return Encode<uint64_t>(aArg); }
      91              :         inline CHIP_ERROR Encode(uint16_t & aArg) const { return Encode<uint64_t>(aArg); }
      92              :         inline CHIP_ERROR Encode(uint8_t const & aArg) const { return Encode<uint64_t>(aArg); }
      93              :         inline CHIP_ERROR Encode(uint8_t & aArg) const { return Encode<uint64_t>(aArg); }
      94              : 
      95              :     private:
      96              :         AttributeValueEncoder & mAttributeValueEncoder;
      97              :         // Avoid calling the TLVWriter constructor for every instantiation of
      98              :         // EncodeListItem.  We treat encoding as a const operation, so either
      99              :         // have to put this on the stack (in which case it's per-instantiation),
     100              :         // or have it as mutable state.
     101              :         mutable TLV::TLVWriter mCheckpoint;
     102              :     };
     103              : 
     104         2471 :     AttributeValueEncoder(AttributeReportIBs::Builder & aAttributeReportIBsBuilder, Access::SubjectDescriptor subjectDescriptor,
     105              :                           const ConcreteAttributePath & aPath, DataVersion aDataVersion, bool aIsFabricFiltered = false,
     106         2471 :                           const AttributeEncodeState & aState = AttributeEncodeState()) :
     107         2471 :         mAttributeReportIBsBuilder(aAttributeReportIBsBuilder),
     108         2471 :         mSubjectDescriptor(subjectDescriptor), mPath(aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId),
     109         2471 :         mDataVersion(aDataVersion), mIsFabricFiltered(aIsFabricFiltered), mEncodeState(aState)
     110         2471 :     {}
     111              : 
     112              :     /**
     113              :      * Encode a single value.  This value will not be chunked; it will either be
     114              :      * entirely encoded or fail to be encoded.  Consumers are allowed to make
     115              :      * either one call to Encode or one call to EncodeList to handle a read.
     116              :      */
     117              :     template <typename... Ts>
     118           36 :     CHIP_ERROR Encode(Ts &&... aArgs)
     119              :     {
     120           36 :         mTriedEncode = true;
     121           36 :         return EncodeAttributeReportIB(std::forward<Ts>(aArgs)...);
     122              :     }
     123              : 
     124              :     /**
     125              :      * Encode an explicit null value.
     126              :      */
     127              :     CHIP_ERROR EncodeNull()
     128              :     {
     129              :         // Doesn't matter what type Nullable we use here.
     130              :         return Encode(DataModel::Nullable<uint8_t>());
     131              :     }
     132              : 
     133              :     /**
     134              :      * Encode an explicit empty list.
     135              :      */
     136              :     CHIP_ERROR EncodeEmptyList()
     137              :     {
     138              :         // Doesn't matter what type List we use here.
     139              :         return Encode(DataModel::List<uint8_t>());
     140              :     }
     141              : 
     142              :     /**
     143              :      * aCallback is expected to take a const auto & argument and Encode() on it as many times as needed to encode all the list
     144              :      * elements one by one.  If any of those Encode() calls returns failure, aCallback must stop encoding and return failure.  When
     145              :      * all items are encoded aCallback is expected to return success.
     146              :      *
     147              :      * aCallback may not be called.  Consumers must not assume it will be called.
     148              :      *
     149              :      * When EncodeList returns an error, the consumers must abort the encoding, and return the exact error to the caller.
     150              :      *
     151              :      * TODO: Can we hold a error state in the AttributeValueEncoder itself?
     152              :      *
     153              :      * Consumers are allowed to make either one call to EncodeList or one call to Encode to handle a read.
     154              :      *
     155              :      */
     156              :     template <typename ListGenerator>
     157         1190 :     CHIP_ERROR EncodeList(ListGenerator aCallback)
     158              :     {
     159         1190 :         mTriedEncode = true;
     160              :         // Spec 10.5.4.3.1, 10.5.4.6 (Replace a list w/ Multiple IBs)
     161              :         // EmptyList acts as the beginning of the whole array type attribute report.
     162              :         // An empty list is encoded iff both mCurrentEncodingListIndex and mEncodeState.mCurrentEncodingListIndex are invalid
     163              :         // values. After encoding the empty list, mEncodeState.mCurrentEncodingListIndex and mCurrentEncodingListIndex are set to 0.
     164         1190 :         ReturnErrorOnFailure(EnsureListStarted());
     165         1134 :         CHIP_ERROR err = aCallback(ListEncodeHelper(*this));
     166              : 
     167              :         // Even if encoding list items failed, make sure we EnsureListEnded().
     168              :         // Since we encode list items atomically, in the case when we just
     169              :         // didn't fit the next item we want to make sure our list is properly
     170              :         // ended before the reporting engine starts chunking.
     171         1134 :         EnsureListEnded();
     172         1134 :         if (err == CHIP_NO_ERROR)
     173              :         {
     174              :             // The Encode procedure finished without any error, clear the state.
     175         1089 :             mEncodeState.Reset();
     176              :         }
     177         1134 :         return err;
     178              :     }
     179              : 
     180         1625 :     bool TriedEncode() const { return mTriedEncode; }
     181              : 
     182              :     const Access::SubjectDescriptor & GetSubjectDescriptor() const { return mSubjectDescriptor; }
     183              : 
     184              :     /**
     185              :      * The accessing fabric index for this read or subscribe interaction.
     186              :      */
     187              :     FabricIndex AccessingFabricIndex() const { return GetSubjectDescriptor().fabricIndex; }
     188              : 
     189              :     /**
     190              :      * AttributeValueEncoder is a short lived object, and the state is persisted by mEncodeState and restored by constructor.
     191              :      */
     192          228 :     const AttributeEncodeState & GetState() const { return mEncodeState; }
     193              : 
     194              : private:
     195              :     // We made EncodeListItem() private, and ListEncoderHelper will expose it by Encode()
     196              :     friend class ListEncodeHelper;
     197              :     friend class TestOnlyAttributeValueEncoderAccessor;
     198              : 
     199              :     // Returns true if the list item should be encoded.  If it should, the
     200              :     // passed-in TLVWriter will be used to checkpoint the current state of our
     201              :     // attribute report list builder.
     202              :     bool ShouldEncodeListItem(TLV::TLVWriter & aCheckpoint);
     203              : 
     204              :     // Does any cleanup work needed after attempting to encode a list item.
     205              :     void PostEncodeListItem(CHIP_ERROR aEncodeStatus, const TLV::TLVWriter & aCheckpoint);
     206              : 
     207              :     // EncodeListItem may be given an extra FabricIndex argument as a second
     208              :     // arg, or not.  Represent that via a parameter pack (which might be
     209              :     // empty). In practice, for any given ItemType the extra arg is either there
     210              :     // or not, so we don't get more template explosion due to aExtraArgs.
     211              :     template <typename ItemType, typename... ExtraArgTypes>
     212         1986 :     CHIP_ERROR EncodeListItem(TLV::TLVWriter & aCheckpoint, const ItemType & aItem, ExtraArgTypes &&... aExtraArgs)
     213              :     {
     214         1986 :         if (!ShouldEncodeListItem(aCheckpoint))
     215              :         {
     216          101 :             return CHIP_NO_ERROR;
     217              :         }
     218              : 
     219              :         CHIP_ERROR err;
     220         1885 :         if (mEncodingInitialList)
     221              :         {
     222              :             // Just encode a single item, with an anonymous tag.
     223              :             AttributeReportBuilder builder;
     224         1801 :             err = builder.EncodeValue(mAttributeReportIBsBuilder, TLV::AnonymousTag(), aItem,
     225              :                                       std::forward<ExtraArgTypes>(aExtraArgs)...);
     226              :         }
     227              :         else
     228              :         {
     229           84 :             err = EncodeAttributeReportIB(aItem, std::forward<ExtraArgTypes>(aExtraArgs)...);
     230              :         }
     231              : 
     232         1885 :         PostEncodeListItem(err, aCheckpoint);
     233         1885 :         return err;
     234              :     }
     235              : 
     236              :     /**
     237              :      * Builds a single AttributeReportIB in AttributeReportIBs.  The caller is
     238              :      * responsible for setting up mPath correctly.
     239              :      *
     240              :      * In particular, when we are encoding a single element in the list, mPath
     241              :      * must indicate a null list index to represent an "append" operation.
     242              :      * operation.
     243              :      *
     244              :      * EncodeAttributeReportIB may be given an extra FabricIndex argument as a second
     245              :      * arg, or not.  Represent that via a parameter pack (which might be
     246              :      * empty). In practice, for any given ItemType the extra arg is either
     247              :      * there or not, so we don't get more template explosion due to aExtraArgs.
     248              :      */
     249              :     template <typename ItemType, typename... ExtraArgTypes>
     250          120 :     CHIP_ERROR EncodeAttributeReportIB(const ItemType & aItem, ExtraArgTypes &&... aExtraArgs)
     251              :     {
     252              :         AttributeReportBuilder builder;
     253          120 :         ReturnErrorOnFailure(builder.PrepareAttribute(mAttributeReportIBsBuilder, mPath, mDataVersion));
     254          120 :         ReturnErrorOnFailure(builder.EncodeValue(mAttributeReportIBsBuilder, TLV::ContextTag(AttributeDataIB::Tag::kData), aItem,
     255              :                                                  std::forward<ExtraArgTypes>(aExtraArgs)...));
     256              : 
     257          120 :         return builder.FinishAttribute(mAttributeReportIBsBuilder);
     258              :     }
     259              : 
     260              :     /**
     261              :      * EnsureListStarted sets our mCurrentEncodingListIndex to 0, and:
     262              :      *
     263              :      * * If we are just starting the list, gets us ready to encode list items.
     264              :      *
     265              :      * * If we are continuing a chunked list, guarantees that mPath.mListOp is
     266              :      *   AppendItem after it returns.
     267              :      */
     268              :     CHIP_ERROR EnsureListStarted();
     269              : 
     270              :     /**
     271              :      * EnsureListEnded writes out the end of the list and our attribute data IB,
     272              :      * if we were encoding our initial list
     273              :      */
     274              :     void EnsureListEnded();
     275              : 
     276              :     AttributeReportIBs::Builder & mAttributeReportIBsBuilder;
     277              :     const Access::SubjectDescriptor mSubjectDescriptor;
     278              :     ConcreteDataAttributePath mPath;
     279              :     DataVersion mDataVersion;
     280              :     bool mTriedEncode      = false;
     281              :     bool mIsFabricFiltered = false;
     282              :     // mEncodingInitialList is true if we're encoding a list and we have not
     283              :     // started chunking it yet, so we're encoding a single attribute report IB
     284              :     // for the whole list, not one per item.
     285              :     bool mEncodingInitialList = false;
     286              :     // mEncodedAtLeastOneListItem becomes true once we successfully encode a list item.
     287              :     bool mEncodedAtLeastOneListItem     = false;
     288              :     ListIndex mCurrentEncodingListIndex = kInvalidListIndex;
     289              :     AttributeEncodeState mEncodeState;
     290              : };
     291              : 
     292              : } // namespace app
     293              : } // namespace chip
        

Generated by: LCOV version 2.0-1