Matter SDK Coverage Report
Current view: top level - app/util - attribute-table.cpp (source / functions) Coverage Total Hit
Test: SHA:b879ecb8e99e175eea0a293a888bda853da2b19c Lines: 0.0 % 159 0
Test Date: 2025-01-17 19:00:11 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 <app/util/odd-sized-integers.h>
      27              : #include <lib/core/CHIPConfig.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 :     EmberAfWriteDataInput completeInput = input;
     171              : 
     172            0 :     if (completeInput.changeListener == nullptr)
     173              :     {
     174            0 :         completeInput.SetChangeListener(emberAfGlobalInteractionModelAttributesChangedListener());
     175              :     }
     176              : 
     177            0 :     return emAfWriteAttribute(path, completeInput, false /* overrideReadOnlyAndDataType */);
     178              : }
     179              : 
     180            0 : Status emberAfWriteAttribute(EndpointId endpoint, ClusterId cluster, AttributeId attributeID, uint8_t * dataPtr,
     181              :                              EmberAfAttributeType dataType)
     182              : {
     183              : 
     184            0 :     return emberAfWriteAttribute(
     185            0 :         ConcreteAttributePath(endpoint, cluster, attributeID),
     186            0 :         EmberAfWriteDataInput(dataPtr, dataType).SetChangeListener(emberAfGlobalInteractionModelAttributesChangedListener()));
     187              : }
     188              : 
     189            0 : Status emberAfWriteAttribute(const ConcreteAttributePath & path, const EmberAfWriteDataInput & input)
     190              : {
     191            0 :     EmberAfWriteDataInput completeInput = input;
     192              : 
     193            0 :     if (completeInput.changeListener == nullptr)
     194              :     {
     195            0 :         completeInput.SetChangeListener(emberAfGlobalInteractionModelAttributesChangedListener());
     196              :     }
     197              : 
     198            0 :     return emAfWriteAttribute(path, completeInput, true /* overrideReadOnlyAndDataType */);
     199              : }
     200              : 
     201              : //------------------------------------------------------------------------------
     202              : // Internal Functions
     203              : 
     204              : // Helper for determining whether a value is a null value.
     205              : template <typename T>
     206            0 : static bool IsNullValue(const uint8_t * data)
     207              : {
     208              :     using Traits = app::NumericAttributeTraits<T>;
     209              :     // We don't know how data is aligned, so safely copy it over to the relevant
     210              :     // StorageType value.
     211              :     typename Traits::StorageType val;
     212            0 :     memcpy(&val, data, sizeof(val));
     213            0 :     return Traits::IsNullValue(val);
     214              : }
     215              : 
     216            0 : static bool IsNullValue(const uint8_t * data, uint16_t dataLen, bool isAttributeSigned)
     217              : {
     218            0 :     if (dataLen > 4)
     219              :     {
     220              :         // We don't support this, just like emberAfCompareValues does not.
     221            0 :         return false;
     222              :     }
     223              : 
     224            0 :     switch (dataLen)
     225              :     {
     226            0 :     case 1: {
     227            0 :         if (isAttributeSigned)
     228              :         {
     229            0 :             return IsNullValue<int8_t>(data);
     230              :         }
     231            0 :         return IsNullValue<uint8_t>(data);
     232              :     }
     233            0 :     case 2: {
     234            0 :         if (isAttributeSigned)
     235              :         {
     236            0 :             return IsNullValue<int16_t>(data);
     237              :         }
     238            0 :         return IsNullValue<uint16_t>(data);
     239              :     }
     240            0 :     case 3: {
     241            0 :         if (isAttributeSigned)
     242              :         {
     243            0 :             return IsNullValue<app::OddSizedInteger<3, true>>(data);
     244              :         }
     245            0 :         return IsNullValue<app::OddSizedInteger<3, false>>(data);
     246              :     }
     247            0 :     case 4: {
     248            0 :         if (isAttributeSigned)
     249              :         {
     250            0 :             return IsNullValue<int32_t>(data);
     251              :         }
     252            0 :         return IsNullValue<uint32_t>(data);
     253              :     }
     254              :     }
     255              : 
     256              :     // Not reached.
     257            0 :     return false;
     258              : }
     259              : 
     260              : namespace {
     261              : 
     262              : /**
     263              :  * Helper function to determine whether the attribute value for the given
     264              :  * attribute is changing.  On success, the isChanging outparam will be set to
     265              :  * whether the value is changing.
     266              :  */
     267            0 : Status AttributeValueIsChanging(EndpointId endpoint, ClusterId cluster, AttributeId attributeID,
     268              :                                 const EmberAfAttributeMetadata * metadata, uint8_t * newValueData, bool * isChanging)
     269              : {
     270            0 :     EmberAfAttributeType attributeType = metadata->attributeType;
     271              : 
     272              :     // We don't know how to size our buffer for strings in general, but if the
     273              :     // string happens to fit into our fixed-size buffer, great.
     274            0 :     size_t valueSize               = metadata->size;
     275            0 :     constexpr size_t kMaxValueSize = 16; // ipv6adr
     276            0 :     if (valueSize > kMaxValueSize)
     277              :     {
     278            0 :         if (emberAfIsStringAttributeType(attributeType) || emberAfIsLongStringAttributeType(attributeType))
     279              :         {
     280              :             // It's a string that may not fit in our buffer.  Just claim it's
     281              :             // changing, since we have no way to tell.
     282            0 :             *isChanging = true;
     283            0 :             return Status::Success;
     284              :         }
     285              : 
     286              :         // Very much unexpected
     287            0 :         ChipLogError(Zcl, "Attribute type %d has too-large size %u", attributeType, static_cast<unsigned>(valueSize));
     288            0 :         return Status::ConstraintError;
     289              :     }
     290              : 
     291              :     uint8_t oldValueBuffer[kMaxValueSize];
     292              :     // Cast to uint16_t is safe, because we checked valueSize <= kMaxValueSize above.
     293            0 :     if (emberAfReadAttribute(endpoint, cluster, attributeID, oldValueBuffer, static_cast<uint16_t>(valueSize)) != Status::Success)
     294              :     {
     295              :         // We failed to read the old value, so flag the value as changing to be safe.
     296            0 :         *isChanging = true;
     297            0 :         return Status::Success;
     298              :     }
     299              : 
     300            0 :     if (emberAfIsStringAttributeType(attributeType))
     301              :     {
     302            0 :         size_t oldLength = emberAfStringLength(oldValueBuffer);
     303            0 :         size_t newLength = emberAfStringLength(newValueData);
     304              :         // The first byte of the buffer is the string length, and
     305              :         // oldLength/newLength refer to the number of bytes after that.  We want
     306              :         // to include that first byte in our comparison, because null and empty
     307              :         // string have different values there but both return 0 from
     308              :         // emberAfStringLength.
     309            0 :         *isChanging = (oldLength != newLength) || (memcmp(oldValueBuffer, newValueData, oldLength + 1) != 0);
     310              :     }
     311            0 :     else if (emberAfIsLongStringAttributeType(attributeType))
     312              :     {
     313            0 :         size_t oldLength = emberAfLongStringLength(oldValueBuffer);
     314            0 :         size_t newLength = emberAfLongStringLength(newValueData);
     315              :         // The first two bytes of the buffer are the string length, and
     316              :         // oldLength/newLength refer to the number of bytes after that.  We want
     317              :         // to include those first two bytes in our comparison, because null and
     318              :         // empty string have different values there but both return 0 from
     319              :         // emberAfLongStringLength.
     320            0 :         *isChanging = (oldLength != newLength) || (memcmp(oldValueBuffer, newValueData, oldLength + 2) != 0);
     321              :     }
     322              :     else
     323              :     {
     324            0 :         *isChanging = (memcmp(newValueData, oldValueBuffer, valueSize) != 0);
     325              :     }
     326              : 
     327            0 :     return Status::Success;
     328              : }
     329              : 
     330            0 : Status emAfWriteAttribute(const ConcreteAttributePath & path, const EmberAfWriteDataInput & input, bool overrideReadOnlyAndDataType)
     331              : {
     332            0 :     const EmberAfAttributeMetadata * metadata = nullptr;
     333              :     EmberAfAttributeSearchRecord record;
     334            0 :     record.endpoint    = path.mEndpointId;
     335            0 :     record.clusterId   = path.mClusterId;
     336            0 :     record.attributeId = path.mAttributeId;
     337            0 :     Status status      = emAfReadOrWriteAttribute(&record, &metadata,
     338              :                                                   nullptr, // buffer
     339              :                                                   0,       // buffer size
     340              :                                                   false);  // write?
     341              : 
     342              :     // if we dont support that attribute
     343            0 :     if (metadata == nullptr)
     344              :     {
     345            0 :         ChipLogProgress(Zcl, "WRITE ERR: ep %x clus " ChipLogFormatMEI " attr " ChipLogFormatMEI " not supported", path.mEndpointId,
     346              :                         ChipLogValueMEI(path.mClusterId), ChipLogValueMEI(path.mAttributeId));
     347            0 :         return status;
     348              :     }
     349              : 
     350              :     // if the data type specified by the caller is incorrect
     351            0 :     if (!(overrideReadOnlyAndDataType))
     352              :     {
     353            0 :         if (input.dataType != metadata->attributeType)
     354              :         {
     355            0 :             ChipLogProgress(Zcl, "WRITE ERR: invalid data type");
     356            0 :             return Status::InvalidDataType;
     357              :         }
     358              : 
     359            0 :         if (metadata->IsReadOnly())
     360              :         {
     361            0 :             ChipLogProgress(Zcl, "WRITE ERR: attr not writable");
     362            0 :             return Status::UnsupportedWrite;
     363              :         }
     364              :     }
     365              : 
     366              :     // if the value the attribute is being set to is out of range
     367              :     // return Status::ConstraintError
     368            0 :     if ((metadata->mask & ATTRIBUTE_MASK_MIN_MAX) != 0U)
     369              :     {
     370            0 :         EmberAfDefaultAttributeValue minv = metadata->defaultValue.ptrToMinMaxValue->minValue;
     371            0 :         EmberAfDefaultAttributeValue maxv = metadata->defaultValue.ptrToMinMaxValue->maxValue;
     372            0 :         uint16_t dataLen                  = emberAfAttributeSize(metadata);
     373              :         const uint8_t * minBytes;
     374              :         const uint8_t * maxBytes;
     375            0 :         if (dataLen <= 2)
     376              :         {
     377              :             static_assert(sizeof(minv.defaultValue) == 2, "if statement relies on size of minv.defaultValue being 2");
     378              :             static_assert(sizeof(maxv.defaultValue) == 2, "if statement relies on size of maxv.defaultValue being 2");
     379            0 :             minBytes = reinterpret_cast<const uint8_t *>(&(minv.defaultValue));
     380            0 :             maxBytes = reinterpret_cast<const uint8_t *>(&(maxv.defaultValue));
     381              : // On big endian cpu with length 1 only the second byte counts
     382              : #if (CHIP_CONFIG_BIG_ENDIAN_TARGET)
     383              :             if (dataLen == 1)
     384              :             {
     385              :                 minBytes++;
     386              :                 maxBytes++;
     387              :             }
     388              : #endif // CHIP_CONFIG_BIG_ENDIAN_TARGET
     389              :         }
     390              :         else
     391              :         {
     392            0 :             minBytes = minv.ptrToDefaultValue;
     393            0 :             maxBytes = maxv.ptrToDefaultValue;
     394              :         }
     395              : 
     396            0 :         bool isAttributeSigned = metadata->IsSignedIntegerAttribute();
     397            0 :         bool isOutOfRange      = emberAfCompareValues(minBytes, input.dataPtr, dataLen, isAttributeSigned) == 1 ||
     398            0 :             emberAfCompareValues(maxBytes, input.dataPtr, dataLen, isAttributeSigned) == -1;
     399              : 
     400            0 :         if (isOutOfRange &&
     401              :             // null value is always in-range for a nullable attribute.
     402            0 :             (!metadata->IsNullable() || !IsNullValue(input.dataPtr, dataLen, isAttributeSigned)))
     403              :         {
     404            0 :             return Status::ConstraintError;
     405              :         }
     406              :     }
     407              : 
     408              :     // Check whether anything is actually changing, before we do any work here.
     409              :     bool valueChanging;
     410              :     Status imStatus =
     411            0 :         AttributeValueIsChanging(path.mEndpointId, path.mClusterId, path.mAttributeId, metadata, input.dataPtr, &valueChanging);
     412            0 :     if (imStatus != Status::Success)
     413              :     {
     414            0 :         return imStatus;
     415              :     }
     416              : 
     417            0 :     if (!valueChanging)
     418              :     {
     419              :         // Just do nothing, except triggering reporting if forced.
     420            0 :         if (input.markDirty == MarkAttributeDirty::kYes)
     421              :         {
     422            0 :             emberAfAttributeChanged(path.mEndpointId, path.mClusterId, path.mAttributeId, input.changeListener);
     423              :         }
     424              : 
     425            0 :         return Status::Success;
     426              :     }
     427              : 
     428            0 :     const app::ConcreteAttributePath attributePath(path.mEndpointId, path.mClusterId, path.mAttributeId);
     429              : 
     430              :     // Pre write attribute callback for all attribute changes,
     431              :     // regardless of cluster.
     432            0 :     imStatus = MatterPreAttributeChangeCallback(attributePath, input.dataType, emberAfAttributeSize(metadata), input.dataPtr);
     433            0 :     if (imStatus != Protocols::InteractionModel::Status::Success)
     434              :     {
     435            0 :         return imStatus;
     436              :     }
     437              : 
     438              :     // Pre-write attribute callback specific
     439              :     // to the cluster that the attribute lives in.
     440            0 :     status = emAfClusterPreAttributeChangedCallback(attributePath, input.dataType, emberAfAttributeSize(metadata), input.dataPtr);
     441              : 
     442              :     // Ignore the following write operation and return success
     443            0 :     if (status == Status::WriteIgnored)
     444              :     {
     445            0 :         return Status::Success;
     446              :     }
     447              : 
     448            0 :     if (status != Status::Success)
     449              :     {
     450            0 :         return status;
     451              :     }
     452              : 
     453              :     // write the attribute
     454            0 :     status = emAfReadOrWriteAttribute(&record,
     455              :                                       nullptr, // metadata
     456            0 :                                       input.dataPtr,
     457              :                                       0,     // buffer size - unused
     458              :                                       true); // write?
     459              : 
     460            0 :     if (status != Status::Success)
     461              :     {
     462            0 :         return status;
     463              :     }
     464              : 
     465              :     // Save the attribute to persistent storage if needed
     466              :     // The callee will weed out attributes that do not need to be stored.
     467            0 :     emAfSaveAttributeToStorageIfNeeded(input.dataPtr, path.mEndpointId, path.mClusterId, metadata);
     468              : 
     469            0 :     if (input.markDirty != MarkAttributeDirty::kNo)
     470              :     {
     471            0 :         emberAfAttributeChanged(path.mEndpointId, path.mClusterId, path.mAttributeId, input.changeListener);
     472              :     }
     473              : 
     474              :     // Post write attribute callback for all attributes changes, regardless
     475              :     // of cluster.
     476            0 :     MatterPostAttributeChangeCallback(attributePath, input.dataType, emberAfAttributeSize(metadata), input.dataPtr);
     477              : 
     478              :     // Post-write attribute callback specific
     479              :     // to the cluster that the attribute lives in.
     480            0 :     emAfClusterAttributeChangedCallback(attributePath);
     481              : 
     482            0 :     return Status::Success;
     483              : }
     484              : 
     485              : } // anonymous namespace
     486              : 
     487            0 : Status emberAfReadAttribute(EndpointId endpoint, ClusterId cluster, AttributeId attributeID, uint8_t * dataPtr, uint16_t readLength)
     488              : {
     489            0 :     const EmberAfAttributeMetadata * metadata = nullptr;
     490              :     EmberAfAttributeSearchRecord record;
     491              :     Status status;
     492            0 :     record.endpoint    = endpoint;
     493            0 :     record.clusterId   = cluster;
     494            0 :     record.attributeId = attributeID;
     495            0 :     status             = emAfReadOrWriteAttribute(&record, &metadata, dataPtr, readLength,
     496              :                                                   false); // write?
     497              : 
     498              :     // failed, print debug info
     499            0 :     if (status == Status::ResourceExhausted)
     500              :     {
     501            0 :         ChipLogProgress(Zcl, "READ: attribute size too large for caller");
     502              :     }
     503              : 
     504            0 :     return status;
     505              : }
        

Generated by: LCOV version 2.0-1