Matter SDK Coverage Report
Current view: top level - app - AttributeValueEncoder.h (source / functions) Coverage Total Hit
Test: SHA:397ba024ebd8924c91db27a264c9f87fd44784af Lines: 100.0 % 34 34
Test Date: 2025-07-18 07:12:08 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              : #include <lib/support/BitFlags.h>
      27              : #include <lib/support/BitMask.h>
      28              : 
      29              : #include <type_traits>
      30              : 
      31              : namespace chip {
      32              : namespace app {
      33              : 
      34              : /**
      35              :  * The AttributeValueEncoder is a helper class for filling report payloads into AttributeReportIBs.
      36              :  * The attribute value encoder can be initialized with a AttributeEncodeState for saving and recovering its state between encode
      37              :  * sessions (chunkings).
      38              :  *
      39              :  * When Encode returns recoverable errors (e.g. CHIP_ERROR_NO_MEMORY) the state can be used to initialize the AttributeValueEncoder
      40              :  * for future use on the same attribute path.
      41              :  */
      42              : class AttributeValueEncoder
      43              : {
      44              : private:
      45              :     // Attempt to save flash by reducing the number of instantiations of the
      46              :     // Encode methods for AttributeValueEncoder and ListEncodeHelper.  The idea
      47              :     // here is that some types actually end up encoded as other types anyway
      48              :     // (e.g. all integers are encoded are uint64_t or int64_t), so we might as
      49              :     // well avoid generating template instantiations of our methods (which have
      50              :     // extra logic) for all the different types that end up encoded the same in
      51              :     // the end.
      52              :     //
      53              :     // A type is a "base" type if it can't be treated as any other type for
      54              :     // encoding purposes.  Overloads of BaseEncodableValue can be added for
      55              :     // "non-base" types to return values of a base type.
      56              :     //
      57              :     // It's important here to not collapse together types for which
      58              :     // DataModel::Encode in fact has different behavior (e.g. enum types).
      59              :     template <typename T>
      60              :     static constexpr const T & BaseEncodableValue(const T & aArg)
      61              :     {
      62              :         return aArg;
      63              :     }
      64              :     template <typename T>
      65              :     static constexpr auto BaseEncodableValue(const BitFlags<T> & aArg)
      66              :     {
      67              :         return BaseEncodableValue(aArg.Raw());
      68              :     }
      69              :     template <typename T>
      70              :     static constexpr auto BaseEncodableValue(const BitMask<T> & aArg)
      71              :     {
      72              :         return BaseEncodableValue(aArg.Raw());
      73              :     }
      74              :     static constexpr uint64_t BaseEncodableValue(uint32_t aArg) { return aArg; }
      75              :     static constexpr uint64_t BaseEncodableValue(uint16_t aArg) { return aArg; }
      76              :     static constexpr uint64_t BaseEncodableValue(uint8_t aArg) { return aArg; }
      77              :     static constexpr int64_t BaseEncodableValue(int32_t aArg) { return aArg; }
      78              :     static constexpr int64_t BaseEncodableValue(int16_t aArg) { return aArg; }
      79              :     static constexpr int64_t BaseEncodableValue(int8_t aArg) { return aArg; }
      80              : 
      81              :     // Determines whether a type should be encoded as-is (if IsBaseType<T> is
      82              :     // true) or transformed to a different type by calling BaseEncodableValue()
      83              :     // on it.
      84              :     template <typename T>
      85              :     static constexpr bool IsBaseType = std::is_same_v<const T &, decltype(BaseEncodableValue(std::declval<const T &>()))>;
      86              : 
      87              : public:
      88              :     class ListEncodeHelper
      89              :     {
      90              :     public:
      91            6 :         ListEncodeHelper(AttributeValueEncoder & encoder) : mAttributeValueEncoder(encoder) {}
      92              : 
      93              :         template <typename T, std::enable_if_t<IsBaseType<T> && DataModel::IsFabricScoped<T>::value, bool> = true>
      94              :         CHIP_ERROR Encode(const T & aArg) const
      95              :         {
      96              :             VerifyOrReturnError(aArg.GetFabricIndex() != kUndefinedFabricIndex, CHIP_ERROR_INVALID_FABRIC_INDEX);
      97              : 
      98              :             // If we are encoding for a fabric filtered attribute read and the fabric index does not match that present in the
      99              :             // request, skip encoding this list item.
     100              :             VerifyOrReturnError(!mAttributeValueEncoder.mIsFabricFiltered ||
     101              :                                     aArg.GetFabricIndex() == mAttributeValueEncoder.AccessingFabricIndex(),
     102              :                                 CHIP_NO_ERROR);
     103              :             return mAttributeValueEncoder.EncodeListItem(mCheckpoint, aArg, mAttributeValueEncoder.AccessingFabricIndex());
     104              :         }
     105              : 
     106              :         template <typename T, std::enable_if_t<IsBaseType<T> && !DataModel::IsFabricScoped<T>::value, bool> = true>
     107         2769 :         CHIP_ERROR Encode(const T & aArg) const
     108              :         {
     109         2769 :             return mAttributeValueEncoder.EncodeListItem(mCheckpoint, aArg);
     110              :         }
     111              : 
     112              :         template <typename T, std::enable_if_t<!IsBaseType<T>, bool> = true>
     113              :         CHIP_ERROR Encode(const T & aArg) const
     114              :         {
     115              :             return Encode(BaseEncodableValue(aArg));
     116              :         }
     117              : 
     118              :     private:
     119              :         AttributeValueEncoder & mAttributeValueEncoder;
     120              :         // Avoid calling the TLVWriter constructor for every instantiation of
     121              :         // EncodeListItem.  We treat encoding as a const operation, so either
     122              :         // have to put this on the stack (in which case it's per-instantiation),
     123              :         // or have it as mutable state.
     124              :         mutable TLV::TLVWriter mCheckpoint;
     125              :     };
     126              : 
     127         2471 :     AttributeValueEncoder(AttributeReportIBs::Builder & aAttributeReportIBsBuilder, Access::SubjectDescriptor subjectDescriptor,
     128              :                           const ConcreteAttributePath & aPath, DataVersion aDataVersion, bool aIsFabricFiltered = false,
     129         2471 :                           const AttributeEncodeState & aState = AttributeEncodeState()) :
     130         2471 :         mAttributeReportIBsBuilder(aAttributeReportIBsBuilder),
     131         2471 :         mSubjectDescriptor(subjectDescriptor), mPath(aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId),
     132         2471 :         mDataVersion(aDataVersion), mIsFabricFiltered(aIsFabricFiltered), mEncodeState(aState)
     133         2471 :     {}
     134              : 
     135              :     /**
     136              :      * Encode a single value.  This value will not be chunked; it will either be
     137              :      * entirely encoded or fail to be encoded.  Consumers are allowed to make
     138              :      * either one call to Encode or one call to EncodeList to handle a read.
     139              :      */
     140              :     template <typename T, std::enable_if_t<IsBaseType<T>, bool> = true>
     141           38 :     CHIP_ERROR Encode(const T & aArg)
     142              :     {
     143           38 :         mTriedEncode = true;
     144           38 :         return EncodeAttributeReportIB(aArg);
     145              :     }
     146              : 
     147              :     template <typename T, std::enable_if_t<!IsBaseType<T>, bool> = true>
     148              :     CHIP_ERROR Encode(const T & aArg)
     149              :     {
     150              :         return Encode(BaseEncodableValue(aArg));
     151              :     }
     152              : 
     153              :     /**
     154              :      * Encode an explicit null value.
     155              :      */
     156              :     CHIP_ERROR EncodeNull()
     157              :     {
     158              :         // Doesn't matter what type Nullable we use here.
     159              :         return Encode(DataModel::Nullable<uint8_t>());
     160              :     }
     161              : 
     162              :     /**
     163              :      * Encode an explicit empty list.
     164              :      */
     165              :     CHIP_ERROR EncodeEmptyList()
     166              :     {
     167              :         // Doesn't matter what type List we use here.
     168              :         return Encode(DataModel::List<uint8_t>());
     169              :     }
     170              : 
     171              :     /**
     172              :      * aCallback is expected to take a const auto & argument and Encode() on it as many times as needed to encode all the list
     173              :      * elements one by one.  If any of those Encode() calls returns failure, aCallback must stop encoding and return failure.  When
     174              :      * all items are encoded aCallback is expected to return success.
     175              :      *
     176              :      * aCallback may not be called.  Consumers must not assume it will be called.
     177              :      *
     178              :      * When EncodeList returns an error, the consumers must abort the encoding, and return the exact error to the caller.
     179              :      *
     180              :      * TODO: Can we hold a error state in the AttributeValueEncoder itself?
     181              :      *
     182              :      * Consumers are allowed to make either one call to EncodeList or one call to Encode to handle a read.
     183              :      *
     184              :      */
     185              :     template <typename ListGenerator>
     186         1402 :     CHIP_ERROR EncodeList(ListGenerator aCallback)
     187              :     {
     188         1402 :         mTriedEncode = true;
     189              :         // Spec 10.5.4.3.1, 10.5.4.6 (Replace a list w/ Multiple IBs)
     190              :         // EmptyList acts as the beginning of the whole array type attribute report.
     191              :         // An empty list is encoded iff both mCurrentEncodingListIndex and mEncodeState.mCurrentEncodingListIndex are invalid
     192              :         // values. After encoding the empty list, mEncodeState.mCurrentEncodingListIndex and mCurrentEncodingListIndex are set to 0.
     193         1402 :         ReturnErrorOnFailure(EnsureListStarted());
     194         1347 :         CHIP_ERROR err = aCallback(ListEncodeHelper(*this));
     195              : 
     196              :         // Even if encoding list items failed, make sure we EnsureListEnded().
     197              :         // Since we encode list items atomically, in the case when we just
     198              :         // didn't fit the next item we want to make sure our list is properly
     199              :         // ended before the reporting engine starts chunking.
     200         1347 :         EnsureListEnded();
     201         1347 :         if (err == CHIP_NO_ERROR)
     202              :         {
     203              :             // The Encode procedure finished without any error, clear the state.
     204         1302 :             mEncodeState.Reset();
     205              :         }
     206         1347 :         return err;
     207              :     }
     208              : 
     209         1625 :     bool TriedEncode() const { return mTriedEncode; }
     210              : 
     211              :     const Access::SubjectDescriptor & GetSubjectDescriptor() const { return mSubjectDescriptor; }
     212              : 
     213              :     /**
     214              :      * The accessing fabric index for this read or subscribe interaction.
     215              :      */
     216              :     FabricIndex AccessingFabricIndex() const { return GetSubjectDescriptor().fabricIndex; }
     217              : 
     218              :     /**
     219              :      * AttributeValueEncoder is a short lived object, and the state is persisted by mEncodeState and restored by constructor.
     220              :      */
     221          228 :     const AttributeEncodeState & GetState() const { return mEncodeState; }
     222              : 
     223              : private:
     224              :     // We made EncodeListItem() private, and ListEncoderHelper will expose it by Encode()
     225              :     friend class ListEncodeHelper;
     226              :     friend class TestOnlyAttributeValueEncoderAccessor;
     227              : 
     228              :     // Returns true if the list item should be encoded.  If it should, the
     229              :     // passed-in TLVWriter will be used to checkpoint the current state of our
     230              :     // attribute report list builder.
     231              :     bool ShouldEncodeListItem(TLV::TLVWriter & aCheckpoint);
     232              : 
     233              :     // Does any cleanup work needed after attempting to encode a list item.
     234              :     void PostEncodeListItem(CHIP_ERROR aEncodeStatus, const TLV::TLVWriter & aCheckpoint);
     235              : 
     236              :     // EncodeListItem may be given an extra FabricIndex argument as a second
     237              :     // arg, or not.  Represent that via a parameter pack (which might be
     238              :     // empty). In practice, for any given ItemType the extra arg is either there
     239              :     // or not, so we don't get more template explosion due to aExtraArgs.
     240              :     template <typename ItemType, typename... ExtraArgTypes>
     241         2769 :     CHIP_ERROR EncodeListItem(TLV::TLVWriter & aCheckpoint, const ItemType & aItem, ExtraArgTypes &&... aExtraArgs)
     242              :     {
     243         2769 :         if (!ShouldEncodeListItem(aCheckpoint))
     244              :         {
     245          102 :             return CHIP_NO_ERROR;
     246              :         }
     247              : 
     248              :         CHIP_ERROR err;
     249         2667 :         if (mEncodingInitialList)
     250              :         {
     251              :             // Just encode a single item, with an anonymous tag.
     252              :             AttributeReportBuilder builder;
     253         2581 :             err = builder.EncodeValue(mAttributeReportIBsBuilder, TLV::AnonymousTag(), aItem,
     254              :                                       std::forward<ExtraArgTypes>(aExtraArgs)...);
     255              :         }
     256              :         else
     257              :         {
     258           86 :             err = EncodeAttributeReportIB(aItem, std::forward<ExtraArgTypes>(aExtraArgs)...);
     259              :         }
     260              : 
     261         2667 :         PostEncodeListItem(err, aCheckpoint);
     262         2667 :         return err;
     263              :     }
     264              : 
     265              :     /**
     266              :      * Builds a single AttributeReportIB in AttributeReportIBs.  The caller is
     267              :      * responsible for setting up mPath correctly.
     268              :      *
     269              :      * In particular, when we are encoding a single element in the list, mPath
     270              :      * must indicate a null list index to represent an "append" operation.
     271              :      * operation.
     272              :      *
     273              :      * EncodeAttributeReportIB may be given an extra FabricIndex argument as a second
     274              :      * arg, or not.  Represent that via a parameter pack (which might be
     275              :      * empty). In practice, for any given ItemType the extra arg is either
     276              :      * there or not, so we don't get more template explosion due to aExtraArgs.
     277              :      */
     278              :     template <typename ItemType, typename... ExtraArgTypes>
     279          335 :     CHIP_ERROR EncodeAttributeReportIB(const ItemType & aItem, ExtraArgTypes &&... aExtraArgs)
     280              :     {
     281              :         AttributeReportBuilder builder;
     282          335 :         ReturnErrorOnFailure(builder.PrepareAttribute(mAttributeReportIBsBuilder, mPath, mDataVersion));
     283          335 :         ReturnErrorOnFailure(builder.EncodeValue(mAttributeReportIBsBuilder, TLV::ContextTag(AttributeDataIB::Tag::kData), aItem,
     284              :                                                  std::forward<ExtraArgTypes>(aExtraArgs)...));
     285              : 
     286          335 :         return builder.FinishAttribute(mAttributeReportIBsBuilder);
     287              :     }
     288              : 
     289              :     /**
     290              :      * EnsureListStarted sets our mCurrentEncodingListIndex to 0, and:
     291              :      *
     292              :      * * If we are just starting the list, gets us ready to encode list items.
     293              :      *
     294              :      * * If we are continuing a chunked list, guarantees that mPath.mListOp is
     295              :      *   AppendItem after it returns.
     296              :      */
     297              :     CHIP_ERROR EnsureListStarted();
     298              : 
     299              :     /**
     300              :      * EnsureListEnded writes out the end of the list and our attribute data IB,
     301              :      * if we were encoding our initial list
     302              :      */
     303              :     void EnsureListEnded();
     304              : 
     305              :     AttributeReportIBs::Builder & mAttributeReportIBsBuilder;
     306              :     const Access::SubjectDescriptor mSubjectDescriptor;
     307              :     ConcreteDataAttributePath mPath;
     308              :     DataVersion mDataVersion;
     309              :     bool mTriedEncode      = false;
     310              :     bool mIsFabricFiltered = false;
     311              :     // mEncodingInitialList is true if we're encoding a list and we have not
     312              :     // started chunking it yet, so we're encoding a single attribute report IB
     313              :     // for the whole list, not one per item.
     314              :     bool mEncodingInitialList = false;
     315              :     // mEncodedAtLeastOneListItem becomes true once we successfully encode a list item.
     316              :     bool mEncodedAtLeastOneListItem     = false;
     317              :     ListIndex mCurrentEncodingListIndex = kInvalidListIndex;
     318              :     AttributeEncodeState mEncodeState;
     319              : };
     320              : 
     321              : } // namespace app
     322              : } // namespace chip
        

Generated by: LCOV version 2.0-1