Matter SDK Coverage Report
Current view: top level - app/util - attribute-table.cpp (source / functions) Coverage Total Hit
Test: SHA:3f9cd168e84cd831b7699126f5296f5c5498690f Lines: 0.0 % 151 0
Test Date: 2026-04-27 19:52:19 Functions: 0.0 % 16 0

            Line data    Source code
       1              : /**
       2              :  *
       3              :  *    Copyright (c) 2020 Project CHIP Authors
       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              : #include <app/util/attribute-table.h>
      18              : 
      19              : #include <app/util/attribute-table-detail.h>
      20              : 
      21              : #include <app/util/attribute-storage-detail.h>
      22              : #include <app/util/attribute-storage.h>
      23              : #include <app/util/config.h>
      24              : #include <app/util/ember-strings.h>
      25              : #include <app/util/generic-callbacks.h>
      26              : #include <lib/core/CHIPConfig.h>
      27              : #include <lib/support/odd-sized-integers.h>
      28              : 
      29              : #include <app/reporting/reporting.h>
      30              : #include <protocols/interaction_model/Constants.h>
      31              : 
      32              : #if (CHIP_CONFIG_BIG_ENDIAN_TARGET)
      33              : #define EM_BIG_ENDIAN true
      34              : #else
      35              : #define EM_BIG_ENDIAN false
      36              : #endif
      37              : 
      38              : using chip::Protocols::InteractionModel::Status;
      39              : 
      40              : using namespace chip;
      41              : using namespace chip::app;
      42              : 
      43              : namespace {
      44              : /**
      45              :  * @brief Simple integer comparison function.
      46              :  * Compares two values of a known length as integers.
      47              :  * Signed integer comparison are supported for numbers with length of
      48              :  * 4 (bytes) or less.
      49              :  * The integers are in native endianness.
      50              :  *
      51              :  * @return -1, if val1 is smaller
      52              :  *          0, if they are the same or if two negative numbers with length
      53              :  *          greater than 4 is being compared
      54              :  *          1, if val2 is smaller.
      55              :  *
      56              :  * You can pass in val1 as NULL, which will assume that it is
      57              :  * pointing to an array of all zeroes. This is used so that
      58              :  * default value of NULL is treated as all zeroes.
      59              :  */
      60            0 : int8_t emberAfCompareValues(const uint8_t * val1, const uint8_t * val2, uint16_t len, bool signedNumber)
      61              : {
      62            0 :     if (len == 0)
      63              :     {
      64              :         // no length means nothing to compare.  Shouldn't even happen, since len is sizeof(some-integer-type).
      65            0 :         return 0;
      66              :     }
      67              : 
      68            0 :     if (signedNumber)
      69              :     { // signed number comparison
      70            0 :         if (len <= 4)
      71              :         { // only number with 32-bits or less is supported
      72            0 :             int32_t accum1 = 0x0;
      73            0 :             int32_t accum2 = 0x0;
      74            0 :             int32_t all1s  = -1;
      75              : 
      76            0 :             for (uint16_t i = 0; i < len; i++)
      77              :             {
      78            0 :                 uint8_t j = (val1 == nullptr ? 0 : (EM_BIG_ENDIAN ? val1[i] : val1[(len - 1) - i]));
      79            0 :                 accum1 |= j << (8 * (len - 1 - i));
      80              : 
      81            0 :                 uint8_t k = (EM_BIG_ENDIAN ? val2[i] : val2[(len - 1) - i]);
      82            0 :                 accum2 |= k << (8 * (len - 1 - i));
      83              :             }
      84              : 
      85              :             // sign extending, no need for 32-bits numbers
      86            0 :             if (len < 4)
      87              :             {
      88            0 :                 if ((accum1 & (1 << (8 * len - 1))) != 0)
      89              :                 { // check sign
      90            0 :                     accum1 |= all1s - ((1 << (len * 8)) - 1);
      91              :                 }
      92            0 :                 if ((accum2 & (1 << (8 * len - 1))) != 0)
      93              :                 { // check sign
      94            0 :                     accum2 |= all1s - ((1 << (len * 8)) - 1);
      95              :                 }
      96              :             }
      97              : 
      98            0 :             if (accum1 > accum2)
      99              :             {
     100            0 :                 return 1;
     101              :             }
     102            0 :             if (accum1 < accum2)
     103              :             {
     104            0 :                 return -1;
     105              :             }
     106              : 
     107            0 :             return 0;
     108              :         }
     109              : 
     110              :         // not supported
     111            0 :         return 0;
     112              :     }
     113              : 
     114              :     // regular unsigned number comparison
     115            0 :     for (uint16_t i = 0; i < len; i++)
     116              :     {
     117            0 :         uint8_t j = (val1 == nullptr ? 0 : (EM_BIG_ENDIAN ? val1[i] : val1[(len - 1) - i]));
     118            0 :         uint8_t k = (EM_BIG_ENDIAN ? val2[i] : val2[(len - 1) - i]);
     119              : 
     120            0 :         if (j > k)
     121              :         {
     122            0 :             return 1;
     123              :         }
     124            0 :         if (k > j)
     125              :         {
     126            0 :             return -1;
     127              :         }
     128              :     }
     129            0 :     return 0;
     130              : }
     131              : 
     132              : /**
     133              :  * @brief write an attribute, performing all the checks.
     134              :  *
     135              :  * This function will attempt to write the attribute value from
     136              :  * the provided pointer. This function will only check that the
     137              :  * attribute exists. If it does it will write the value into
     138              :  * the attribute table for the given attribute.
     139              :  *
     140              :  * This function will not check to see if the attribute is
     141              :  * writable since the read only / writable characteristic
     142              :  * of an attribute only pertains to external devices writing
     143              :  * over the air. Because this function is being called locally
     144              :  * it assumes that the device knows what it is doing and has permission
     145              :  * to perform the given operation.
     146              :  *
     147              :  * if true is passed in for overrideReadOnlyAndDataType then the data type is
     148              :  * not checked and the read-only flag is ignored. This mode is meant for
     149              :  * testing or setting the initial value of the attribute on the device.
     150              :  *
     151              :  * this returns:
     152              :  * - Status::UnsupportedEndpoint: if endpoint isn't supported by the device.
     153              :  * - Status::UnsupportedCluster: if cluster isn't supported on the endpoint.
     154              :  * - Status::UnsupportedAttribute: if attribute isn't supported in the cluster.
     155              :  * - Status::InvalidDataType: if the data type passed in doesnt match the type
     156              :  *           stored in the attribute table
     157              :  * - Status::UnsupportedWrite: if the attribute isnt writable
     158              :  * - Status::ConstraintError: if the value is set out of the allowable range for
     159              :  *           the attribute
     160              :  * - Status::Success: if the attribute was found and successfully written
     161              :  */
     162              : Status emAfWriteAttribute(const ConcreteAttributePath & path, const EmberAfWriteDataInput & input,
     163              :                           bool overrideReadOnlyAndDataType);
     164              : 
     165              : } // anonymous namespace
     166              : 
     167            0 : Protocols::InteractionModel::Status emAfWriteAttributeExternal(const ConcreteAttributePath & path,
     168              :                                                                const EmberAfWriteDataInput & input)
     169              : {
     170            0 :     return emAfWriteAttribute(path, input, false /* overrideReadOnlyAndDataType */);
     171              : }
     172              : 
     173            0 : Status emberAfWriteAttribute(EndpointId endpoint, ClusterId cluster, AttributeId attributeID, uint8_t * dataPtr,
     174              :                              EmberAfAttributeType dataType)
     175              : {
     176              : 
     177            0 :     return emberAfWriteAttribute(ConcreteAttributePath(endpoint, cluster, attributeID), EmberAfWriteDataInput(dataPtr, dataType));
     178              : }
     179              : 
     180            0 : Status emberAfWriteAttribute(const ConcreteAttributePath & path, const EmberAfWriteDataInput & input)
     181              : {
     182            0 :     return emAfWriteAttribute(path, input, true /* overrideReadOnlyAndDataType */);
     183              : }
     184              : 
     185              : //------------------------------------------------------------------------------
     186              : // Internal Functions
     187              : 
     188              : // Helper for determining whether a value is a null value.
     189              : template <typename T>
     190            0 : static bool IsNullValue(const uint8_t * data)
     191              : {
     192              :     using Traits = app::NumericAttributeTraits<T>;
     193              :     // We don't know how data is aligned, so safely copy it over to the relevant
     194              :     // StorageType value.
     195              :     typename Traits::StorageType val;
     196            0 :     memcpy(&val, data, sizeof(val));
     197            0 :     return Traits::IsNullValue(val);
     198              : }
     199              : 
     200            0 : static bool IsNullValue(const uint8_t * data, uint16_t dataLen, bool isAttributeSigned)
     201              : {
     202            0 :     if (dataLen > 4)
     203              :     {
     204              :         // We don't support this, just like emberAfCompareValues does not.
     205            0 :         return false;
     206              :     }
     207              : 
     208            0 :     switch (dataLen)
     209              :     {
     210            0 :     case 1: {
     211            0 :         if (isAttributeSigned)
     212              :         {
     213            0 :             return IsNullValue<int8_t>(data);
     214              :         }
     215            0 :         return IsNullValue<uint8_t>(data);
     216              :     }
     217            0 :     case 2: {
     218            0 :         if (isAttributeSigned)
     219              :         {
     220            0 :             return IsNullValue<int16_t>(data);
     221              :         }
     222            0 :         return IsNullValue<uint16_t>(data);
     223              :     }
     224            0 :     case 3: {
     225            0 :         if (isAttributeSigned)
     226              :         {
     227            0 :             return IsNullValue<app::OddSizedInteger<3, true>>(data);
     228              :         }
     229            0 :         return IsNullValue<app::OddSizedInteger<3, false>>(data);
     230              :     }
     231            0 :     case 4: {
     232            0 :         if (isAttributeSigned)
     233              :         {
     234            0 :             return IsNullValue<int32_t>(data);
     235              :         }
     236            0 :         return IsNullValue<uint32_t>(data);
     237              :     }
     238              :     }
     239              : 
     240              :     // Not reached.
     241            0 :     return false;
     242              : }
     243              : 
     244              : namespace {
     245              : 
     246              : /**
     247              :  * Helper function to determine whether the attribute value for the given
     248              :  * attribute is changing.  On success, the isChanging outparam will be set to
     249              :  * whether the value is changing.
     250              :  */
     251            0 : Status AttributeValueIsChanging(EndpointId endpoint, ClusterId cluster, AttributeId attributeID,
     252              :                                 const EmberAfAttributeMetadata * metadata, uint8_t * newValueData, bool * isChanging)
     253              : {
     254            0 :     EmberAfAttributeType attributeType = metadata->attributeType;
     255              : 
     256              :     // We don't know how to size our buffer for strings in general, but if the
     257              :     // string happens to fit into our fixed-size buffer, great.
     258            0 :     size_t valueSize               = metadata->size;
     259            0 :     constexpr size_t kMaxValueSize = 16; // ipv6adr
     260            0 :     if (valueSize > kMaxValueSize)
     261              :     {
     262            0 :         if (emberAfIsStringAttributeType(attributeType) || emberAfIsLongStringAttributeType(attributeType))
     263              :         {
     264              :             // It's a string that may not fit in our buffer.  Just claim it's
     265              :             // changing, since we have no way to tell.
     266            0 :             *isChanging = true;
     267            0 :             return Status::Success;
     268              :         }
     269              : 
     270              :         // Very much unexpected
     271            0 :         ChipLogError(Zcl, "Attribute type %d has too-large size %u", attributeType, static_cast<unsigned>(valueSize));
     272            0 :         return Status::ConstraintError;
     273              :     }
     274              : 
     275              :     uint8_t oldValueBuffer[kMaxValueSize];
     276              :     // Cast to uint16_t is safe, because we checked valueSize <= kMaxValueSize above.
     277            0 :     if (emberAfReadAttribute(endpoint, cluster, attributeID, oldValueBuffer, static_cast<uint16_t>(valueSize)) != Status::Success)
     278              :     {
     279              :         // We failed to read the old value, so flag the value as changing to be safe.
     280            0 :         *isChanging = true;
     281            0 :         return Status::Success;
     282              :     }
     283              : 
     284            0 :     if (emberAfIsStringAttributeType(attributeType))
     285              :     {
     286            0 :         size_t oldLength = emberAfStringLength(oldValueBuffer);
     287            0 :         size_t newLength = emberAfStringLength(newValueData);
     288              :         // The first byte of the buffer is the string length, and
     289              :         // oldLength/newLength refer to the number of bytes after that.  We want
     290              :         // to include that first byte in our comparison, because null and empty
     291              :         // string have different values there but both return 0 from
     292              :         // emberAfStringLength.
     293            0 :         *isChanging = (oldLength != newLength) || (memcmp(oldValueBuffer, newValueData, oldLength + 1) != 0);
     294              :     }
     295            0 :     else if (emberAfIsLongStringAttributeType(attributeType))
     296              :     {
     297            0 :         size_t oldLength = emberAfLongStringLength(oldValueBuffer);
     298            0 :         size_t newLength = emberAfLongStringLength(newValueData);
     299              :         // The first two bytes of the buffer are the string length, and
     300              :         // oldLength/newLength refer to the number of bytes after that.  We want
     301              :         // to include those first two bytes in our comparison, because null and
     302              :         // empty string have different values there but both return 0 from
     303              :         // emberAfLongStringLength.
     304            0 :         *isChanging = (oldLength != newLength) || (memcmp(oldValueBuffer, newValueData, oldLength + 2) != 0);
     305              :     }
     306              :     else
     307              :     {
     308            0 :         *isChanging = (memcmp(newValueData, oldValueBuffer, valueSize) != 0);
     309              :     }
     310              : 
     311            0 :     return Status::Success;
     312              : }
     313              : 
     314            0 : Status emAfWriteAttribute(const ConcreteAttributePath & path, const EmberAfWriteDataInput & input, bool overrideReadOnlyAndDataType)
     315              : {
     316            0 :     const EmberAfAttributeMetadata * metadata = nullptr;
     317              :     EmberAfAttributeSearchRecord record;
     318            0 :     record.endpoint    = path.mEndpointId;
     319            0 :     record.clusterId   = path.mClusterId;
     320            0 :     record.attributeId = path.mAttributeId;
     321            0 :     Status status      = emAfReadOrWriteAttribute(&record, &metadata,
     322              :                                                   nullptr, // buffer
     323              :                                                   0,       // buffer size
     324              :                                                   false);  // write?
     325              : 
     326              :     // if we dont support that attribute
     327            0 :     if (metadata == nullptr)
     328              :     {
     329            0 :         ChipLogProgress(Zcl, "WRITE ERR: ep %x clus " ChipLogFormatMEI " attr " ChipLogFormatMEI " not supported", path.mEndpointId,
     330              :                         ChipLogValueMEI(path.mClusterId), ChipLogValueMEI(path.mAttributeId));
     331            0 :         return status;
     332              :     }
     333              : 
     334              :     // if the data type specified by the caller is incorrect
     335            0 :     if (!(overrideReadOnlyAndDataType))
     336              :     {
     337            0 :         if (input.dataType != metadata->attributeType)
     338              :         {
     339            0 :             ChipLogProgress(Zcl, "WRITE ERR: invalid data type");
     340            0 :             return Status::InvalidDataType;
     341              :         }
     342              : 
     343            0 :         if (!metadata->IsWritable())
     344              :         {
     345            0 :             ChipLogProgress(Zcl, "WRITE ERR: attr not writable");
     346            0 :             return Status::UnsupportedWrite;
     347              :         }
     348              :     }
     349              : 
     350              :     // if the value the attribute is being set to is out of range
     351              :     // return Status::ConstraintError
     352            0 :     if ((metadata->mask & MATTER_ATTRIBUTE_FLAG_MIN_MAX) != 0U)
     353              :     {
     354            0 :         EmberAfDefaultAttributeValue minv = metadata->defaultValue.ptrToMinMaxValue->minValue;
     355            0 :         EmberAfDefaultAttributeValue maxv = metadata->defaultValue.ptrToMinMaxValue->maxValue;
     356            0 :         uint16_t dataLen                  = emberAfAttributeSize(metadata);
     357              :         const uint8_t * minBytes;
     358              :         const uint8_t * maxBytes;
     359            0 :         if (dataLen <= 2)
     360              :         {
     361              :             static_assert(sizeof(minv.defaultValue) == 2, "if statement relies on size of minv.defaultValue being 2");
     362              :             static_assert(sizeof(maxv.defaultValue) == 2, "if statement relies on size of maxv.defaultValue being 2");
     363            0 :             minBytes = reinterpret_cast<const uint8_t *>(&(minv.defaultValue));
     364            0 :             maxBytes = reinterpret_cast<const uint8_t *>(&(maxv.defaultValue));
     365              : // On big endian cpu with length 1 only the second byte counts
     366              : #if (CHIP_CONFIG_BIG_ENDIAN_TARGET)
     367              :             if (dataLen == 1)
     368              :             {
     369              :                 minBytes++;
     370              :                 maxBytes++;
     371              :             }
     372              : #endif // CHIP_CONFIG_BIG_ENDIAN_TARGET
     373              :         }
     374              :         else
     375              :         {
     376            0 :             minBytes = minv.ptrToDefaultValue;
     377            0 :             maxBytes = maxv.ptrToDefaultValue;
     378              :         }
     379              : 
     380            0 :         bool isAttributeSigned = metadata->IsSignedIntegerAttribute();
     381            0 :         bool isOutOfRange      = emberAfCompareValues(minBytes, input.dataPtr, dataLen, isAttributeSigned) == 1 ||
     382            0 :             emberAfCompareValues(maxBytes, input.dataPtr, dataLen, isAttributeSigned) == -1;
     383              : 
     384            0 :         if (isOutOfRange &&
     385              :             // null value is always in-range for a nullable attribute.
     386            0 :             (!metadata->IsNullable() || !IsNullValue(input.dataPtr, dataLen, isAttributeSigned)))
     387              :         {
     388            0 :             return Status::ConstraintError;
     389              :         }
     390              :     }
     391              : 
     392              :     // Check whether anything is actually changing, before we do any work here.
     393              :     bool valueChanging;
     394              :     Status imStatus =
     395            0 :         AttributeValueIsChanging(path.mEndpointId, path.mClusterId, path.mAttributeId, metadata, input.dataPtr, &valueChanging);
     396            0 :     if (imStatus != Status::Success)
     397              :     {
     398            0 :         return imStatus;
     399              :     }
     400              : 
     401            0 :     if (!valueChanging)
     402              :     {
     403              :         // Just do nothing, except triggering reporting if forced.
     404            0 :         if (input.markDirty == MarkAttributeDirty::kYes)
     405              :         {
     406            0 :             emberAfAttributeChanged(path.mEndpointId, path.mClusterId, path.mAttributeId);
     407              :         }
     408              : 
     409            0 :         return Status::Success;
     410              :     }
     411              : 
     412            0 :     const app::ConcreteAttributePath attributePath(path.mEndpointId, path.mClusterId, path.mAttributeId);
     413              : 
     414              :     // Pre write attribute callback for all attribute changes,
     415              :     // regardless of cluster.
     416            0 :     imStatus = MatterPreAttributeChangeCallback(attributePath, input.dataType, emberAfAttributeSize(metadata), input.dataPtr);
     417            0 :     if (imStatus != Protocols::InteractionModel::Status::Success)
     418              :     {
     419            0 :         return imStatus;
     420              :     }
     421              : 
     422              :     // Pre-write attribute callback specific
     423              :     // to the cluster that the attribute lives in.
     424            0 :     status = emAfClusterPreAttributeChangedCallback(attributePath, input.dataType, emberAfAttributeSize(metadata), input.dataPtr);
     425              : 
     426              :     // Ignore the following write operation and return success
     427            0 :     if (status == Status::WriteIgnored)
     428              :     {
     429            0 :         return Status::Success;
     430              :     }
     431              : 
     432            0 :     if (status != Status::Success)
     433              :     {
     434            0 :         return status;
     435              :     }
     436              : 
     437              :     // write the attribute
     438            0 :     status = emAfReadOrWriteAttribute(&record,
     439              :                                       nullptr, // metadata
     440            0 :                                       input.dataPtr,
     441              :                                       0,     // buffer size - unused
     442              :                                       true); // write?
     443              : 
     444            0 :     if (status != Status::Success)
     445              :     {
     446            0 :         return status;
     447              :     }
     448              : 
     449              :     // Save the attribute to persistent storage if needed
     450              :     // The callee will weed out attributes that do not need to be stored.
     451            0 :     emAfSaveAttributeToStorageIfNeeded(input.dataPtr, path.mEndpointId, path.mClusterId, metadata);
     452              : 
     453            0 :     if (input.markDirty != MarkAttributeDirty::kNo)
     454              :     {
     455            0 :         emberAfAttributeChanged(path.mEndpointId, path.mClusterId, path.mAttributeId);
     456              :     }
     457              : 
     458              :     // Post write attribute callback for all attributes changes, regardless
     459              :     // of cluster.
     460            0 :     MatterPostAttributeChangeCallback(attributePath, input.dataType, emberAfAttributeSize(metadata), input.dataPtr);
     461              : 
     462              :     // Post-write attribute callback specific
     463              :     // to the cluster that the attribute lives in.
     464            0 :     emAfClusterAttributeChangedCallback(attributePath);
     465              : 
     466            0 :     return Status::Success;
     467              : }
     468              : 
     469              : } // anonymous namespace
     470              : 
     471            0 : Status emberAfReadAttribute(EndpointId endpoint, ClusterId cluster, AttributeId attributeID, uint8_t * dataPtr, uint16_t readLength)
     472              : {
     473            0 :     const EmberAfAttributeMetadata * metadata = nullptr;
     474              :     EmberAfAttributeSearchRecord record;
     475              :     Status status;
     476            0 :     record.endpoint    = endpoint;
     477            0 :     record.clusterId   = cluster;
     478            0 :     record.attributeId = attributeID;
     479            0 :     status             = emAfReadOrWriteAttribute(&record, &metadata, dataPtr, readLength,
     480              :                                                   false); // write?
     481              : 
     482              :     // failed, print debug info
     483            0 :     if (status == Status::ResourceExhausted)
     484              :     {
     485            0 :         ChipLogProgress(Zcl, "READ: attribute size too large for caller");
     486              :     }
     487              : 
     488            0 :     return status;
     489              : }
        

Generated by: LCOV version 2.0-1