Matter SDK Coverage Report
Current view: top level - app - AttributeValueEncoder.h (source / functions) Coverage Total Hit
Test: SHA:db08debc068562b264a2df3a7f3a8cc1d0b3aba1 Lines: 85.5 % 62 53
Test Date: 2025-10-02 07:10:30 Functions: 37.7 % 215 81

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

Generated by: LCOV version 2.0-1