Matter SDK Coverage Report
Current view: top level - data-model-providers/codegen - CodegenDataModelProvider_Write.cpp (source / functions) Coverage Total Hit
Test: SHA:b879ecb8e99e175eea0a293a888bda853da2b19c Lines: 87.8 % 74 65
Test Date: 2025-01-17 19:00:11 Functions: 80.0 % 5 4

            Line data    Source code
       1              : /*
       2              :  *    Copyright (c) 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              : #include <data-model-providers/codegen/CodegenDataModelProvider.h>
      18              : 
      19              : #include <access/AccessControl.h>
      20              : #include <app-common/zap-generated/attribute-type.h>
      21              : #include <app/AttributeAccessInterface.h>
      22              : #include <app/AttributeAccessInterfaceRegistry.h>
      23              : #include <app/RequiredPrivilege.h>
      24              : #include <app/data-model/FabricScoped.h>
      25              : #include <app/reporting/reporting.h>
      26              : #include <app/util/af-types.h>
      27              : #include <app/util/attribute-metadata.h>
      28              : #include <app/util/attribute-storage-detail.h>
      29              : #include <app/util/attribute-storage-null-handling.h>
      30              : #include <app/util/attribute-storage.h>
      31              : #include <app/util/attribute-table-detail.h>
      32              : #include <app/util/attribute-table.h>
      33              : #include <app/util/ember-io-storage.h>
      34              : #include <app/util/ember-strings.h>
      35              : #include <app/util/odd-sized-integers.h>
      36              : #include <data-model-providers/codegen/EmberAttributeDataBuffer.h>
      37              : #include <data-model-providers/codegen/EmberMetadata.h>
      38              : #include <lib/core/CHIPError.h>
      39              : #include <lib/support/CodeUtils.h>
      40              : 
      41              : #include <zap-generated/endpoint_config.h>
      42              : 
      43              : namespace chip {
      44              : namespace app {
      45              : namespace {
      46              : 
      47              : using namespace chip::app::Compatibility::Internal;
      48              : using Protocols::InteractionModel::Status;
      49              : 
      50              : class ContextAttributesChangeListener : public AttributesChangedListener
      51              : {
      52              : public:
      53         2579 :     ContextAttributesChangeListener(const DataModel::InteractionModelContext & context) : mListener(context.dataModelChangeListener)
      54         2579 :     {}
      55         2546 :     void MarkDirty(const AttributePathParams & path) override { mListener->MarkDirty(path); }
      56              : 
      57              : private:
      58              :     DataModel::ProviderChangeListener * mListener;
      59              : };
      60              : 
      61              : /// Attempts to write via an attribute access interface (AAI)
      62              : ///
      63              : /// If it returns a CHIP_ERROR, then this is a FINAL result (i.e. either failure or success)
      64              : ///
      65              : /// If it returns std::nullopt, then there is no AAI to handle the given path
      66              : /// and processing should figure out the value otherwise (generally from other ember data)
      67         2579 : std::optional<CHIP_ERROR> TryWriteViaAccessInterface(const ConcreteDataAttributePath & path, AttributeAccessInterface * aai,
      68              :                                                      AttributeValueDecoder & decoder)
      69              : {
      70              :     // Processing can happen only if an attribute access interface actually exists..
      71         2579 :     if (aai == nullptr)
      72              :     {
      73          139 :         return std::nullopt;
      74              :     }
      75              : 
      76         2440 :     CHIP_ERROR err = aai->Write(path, decoder);
      77              : 
      78         2440 :     if (err != CHIP_NO_ERROR)
      79              :     {
      80            6 :         return std::make_optional(err);
      81              :     }
      82              : 
      83              :     // If the decoder tried to decode, then a value should have been read for processing.
      84              :     //   - if decoding was done, assume DONE (i.e. final CHIP_NO_ERROR)
      85              :     //   -  otherwise, if no decoding done, return that processing must continue via nullopt
      86         4868 :     return decoder.TriedDecode() ? std::make_optional(CHIP_NO_ERROR) : std::nullopt;
      87              : }
      88              : 
      89              : } // namespace
      90              : 
      91         2587 : DataModel::ActionReturnStatus CodegenDataModelProvider::WriteAttribute(const DataModel::WriteAttributeRequest & request,
      92              :                                                                        AttributeValueDecoder & decoder)
      93              : {
      94         2587 :     ChipLogDetail(DataManagement, "Writing attribute: Cluster=" ChipLogFormatMEI " Endpoint=0x%x AttributeId=" ChipLogFormatMEI,
      95              :                   ChipLogValueMEI(request.path.mClusterId), request.path.mEndpointId, ChipLogValueMEI(request.path.mAttributeId));
      96              : 
      97              :     // TODO: ordering is to check writability/existence BEFORE ACL and this seems wrong, however
      98              :     //       existing unit tests (TC_AcessChecker.py) validate that we get UnsupportedWrite instead of UnsupportedAccess
      99              :     //
     100              :     //       This should likely be fixed in spec (probably already fixed by
     101              :     //       https://github.com/CHIP-Specifications/connectedhomeip-spec/pull/9024)
     102              :     //       and tests and implementation
     103              :     //
     104              :     //       Open issue that needs fixing: https://github.com/project-chip/connectedhomeip/issues/33735
     105         2587 :     auto metadata = Ember::FindAttributeMetadata(request.path);
     106              : 
     107              :     // Explicit failure in finding a suitable metadata
     108         2587 :     if (const Status * status = std::get_if<Status>(&metadata))
     109              :     {
     110            3 :         VerifyOrDie((*status == Status::UnsupportedEndpoint) || //
     111              :                     (*status == Status::UnsupportedCluster) ||  //
     112              :                     (*status == Status::UnsupportedAttribute));
     113            3 :         return *status;
     114              :     }
     115              : 
     116         2584 :     const EmberAfAttributeMetadata ** attributeMetadata = std::get_if<const EmberAfAttributeMetadata *>(&metadata);
     117              : 
     118              :     // All the global attributes that we do not have metadata for are
     119              :     // read-only. Specifically only the following list-based attributes match the
     120              :     // "global attributes not in metadata" (see GlobalAttributes.h :: GlobalAttributesNotInMetadata):
     121              :     //   - AttributeList
     122              :     //   - EventList
     123              :     //   - AcceptedCommands
     124              :     //   - GeneratedCommands
     125              :     //
     126              :     // Given the above, UnsupportedWrite should be correct (attempt to write to a read-only list)
     127         2584 :     bool isReadOnly = (attributeMetadata == nullptr) || (*attributeMetadata)->IsReadOnly();
     128              : 
     129              :     // Internal is allowed to bypass timed writes and read-only.
     130         2584 :     if (!request.operationFlags.Has(DataModel::OperationFlags::kInternal))
     131              :     {
     132         2583 :         VerifyOrReturnError(!isReadOnly, Status::UnsupportedWrite);
     133              :     }
     134              : 
     135              :     // ACL check for non-internal requests
     136         2582 :     bool checkAcl = !request.operationFlags.Has(DataModel::OperationFlags::kInternal);
     137              : 
     138              :     // For chunking, ACL check is not re-done if the previous write was successful for the exact same
     139              :     // path. We apply this everywhere as a shortcut, although realistically this is only for AccessControl cluster
     140         2582 :     if (checkAcl && request.previousSuccessPath.has_value())
     141              :     {
     142              :         // NOTE: explicit cast/check only for attribute path and nothing else.
     143              :         //
     144              :         //       In particular `request.path` is a DATA path (contains a list index)
     145              :         //       and we do not want request.previousSuccessPath to be auto-cast to a
     146              :         //       data path with a empty list and fail the compare.
     147              :         //
     148              :         //       This could be `request.previousSuccessPath != request.path` (where order
     149              :         //       is important) however that would seem more brittle (relying that a != b
     150              :         //       behaves differently than b != a due to casts). Overall Data paths are not
     151              :         //       the same as attribute paths.
     152              :         //
     153              :         //       Also note that Concrete path have a mExpanded that is not used in compares.
     154          720 :         const ConcreteAttributePath & attributePathA = request.path;
     155          720 :         const ConcreteAttributePath & attributePathB = *request.previousSuccessPath;
     156              : 
     157          720 :         checkAcl = (attributePathA != attributePathB);
     158              :     }
     159              : 
     160         2582 :     if (checkAcl)
     161              :     {
     162         1864 :         VerifyOrReturnError(request.subjectDescriptor != nullptr, Status::UnsupportedAccess);
     163              : 
     164         1863 :         Access::RequestPath requestPath{ .cluster     = request.path.mClusterId,
     165         1863 :                                          .endpoint    = request.path.mEndpointId,
     166              :                                          .requestType = Access::RequestType::kAttributeWriteRequest,
     167         1863 :                                          .entityId    = request.path.mAttributeId };
     168         1863 :         CHIP_ERROR err = Access::GetAccessControl().Check(*request.subjectDescriptor, requestPath,
     169              :                                                           RequiredPrivilege::ForWriteAttribute(request.path));
     170              : 
     171         1863 :         if (err != CHIP_NO_ERROR)
     172              :         {
     173            1 :             VerifyOrReturnValue(err != CHIP_ERROR_ACCESS_DENIED, Status::UnsupportedAccess);
     174            0 :             VerifyOrReturnValue(err != CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL, Status::AccessRestricted);
     175              : 
     176            0 :             return err;
     177              :         }
     178              :     }
     179              : 
     180              :     // Internal is allowed to bypass timed writes and read-only.
     181         2581 :     if (!request.operationFlags.Has(DataModel::OperationFlags::kInternal))
     182              :     {
     183         2580 :         VerifyOrReturnError(!(*attributeMetadata)->MustUseTimedWrite() || request.writeFlags.Has(DataModel::WriteFlags::kTimed),
     184              :                             Status::NeedsTimedInteraction);
     185              :     }
     186              : 
     187              :     // Extra check: internal requests can bypass the read only check, however global attributes
     188              :     // have no underlying storage, so write still cannot be done
     189         2580 :     VerifyOrReturnError(attributeMetadata != nullptr, Status::UnsupportedWrite);
     190              : 
     191         2580 :     if (request.path.mDataVersion.HasValue())
     192              :     {
     193            2 :         std::optional<DataModel::ClusterInfo> clusterInfo = GetServerClusterInfo(request.path);
     194            2 :         if (!clusterInfo.has_value())
     195              :         {
     196            0 :             ChipLogError(DataManagement, "Unable to get cluster info for Endpoint 0x%x, Cluster " ChipLogFormatMEI,
     197              :                          request.path.mEndpointId, ChipLogValueMEI(request.path.mClusterId));
     198            1 :             return Status::DataVersionMismatch;
     199              :         }
     200              : 
     201            2 :         if (request.path.mDataVersion.Value() != clusterInfo->dataVersion)
     202              :         {
     203            1 :             ChipLogError(DataManagement, "Write Version mismatch for Endpoint 0x%x, Cluster " ChipLogFormatMEI,
     204              :                          request.path.mEndpointId, ChipLogValueMEI(request.path.mClusterId));
     205            1 :             return Status::DataVersionMismatch;
     206              :         }
     207              :     }
     208              : 
     209         2579 :     ContextAttributesChangeListener change_listener(CurrentContext());
     210              : 
     211              :     AttributeAccessInterface * aai =
     212         2579 :         AttributeAccessInterfaceRegistry::Instance().Get(request.path.mEndpointId, request.path.mClusterId);
     213         2579 :     std::optional<CHIP_ERROR> aai_result = TryWriteViaAccessInterface(request.path, aai, decoder);
     214         2579 :     if (aai_result.has_value())
     215              :     {
     216         2436 :         if (*aai_result == CHIP_NO_ERROR)
     217              :         {
     218              :             // TODO: this is awkward since it provides AAI no control over this, specifically
     219              :             //       AAI may not want to increase versions for some attributes that are Q
     220         2430 :             emberAfAttributeChanged(request.path.mEndpointId, request.path.mClusterId, request.path.mAttributeId, &change_listener);
     221              :         }
     222         2436 :         return *aai_result;
     223              :     }
     224              : 
     225          143 :     MutableByteSpan dataBuffer = gEmberAttributeIOBufferSpan;
     226              :     {
     227          143 :         Ember::EmberAttributeDataBuffer emberData(*attributeMetadata, dataBuffer);
     228          143 :         ReturnErrorOnFailure(decoder.Decode(emberData));
     229              :     }
     230              : 
     231              :     Protocols::InteractionModel::Status status;
     232              : 
     233          119 :     if (dataBuffer.size() > (*attributeMetadata)->size)
     234              :     {
     235            1 :         ChipLogDetail(Zcl, "Data to write exceeds the attribute size claimed.");
     236            1 :         return Status::InvalidValue;
     237              :     }
     238              : 
     239          118 :     EmberAfWriteDataInput dataInput(dataBuffer.data(), (*attributeMetadata)->attributeType);
     240              : 
     241          118 :     dataInput.SetChangeListener(&change_listener);
     242              :     // TODO: dataInput.SetMarkDirty() should be according to `ChangesOmmited`
     243              : 
     244          118 :     if (request.operationFlags.Has(DataModel::OperationFlags::kInternal))
     245              :     {
     246              :         // Internal requests use the non-External interface that has less enforcement
     247              :         // than the external version (e.g. does not check/enforce writable settings, does not
     248              :         // validate attribute types) - see attribute-table.h documentation for details.
     249            1 :         status = emberAfWriteAttribute(request.path, dataInput);
     250              :     }
     251              :     else
     252              :     {
     253          117 :         status = emAfWriteAttributeExternal(request.path, dataInput);
     254              :     }
     255              : 
     256          118 :     if (status != Protocols::InteractionModel::Status::Success)
     257              :     {
     258            2 :         return status;
     259              :     }
     260              : 
     261          116 :     return CHIP_NO_ERROR;
     262         2579 : }
     263              : 
     264            0 : void CodegenDataModelProvider::Temporary_ReportAttributeChanged(const AttributePathParams & path)
     265              : {
     266            0 :     ContextAttributesChangeListener change_listener(CurrentContext());
     267            0 :     if (path.mClusterId != kInvalidClusterId)
     268              :     {
     269            0 :         emberAfAttributeChanged(path.mEndpointId, path.mClusterId, path.mAttributeId, &change_listener);
     270              :     }
     271              :     else
     272              :     {
     273              :         // When the path has wildcard cluster Id, call the emberAfEndpointChanged to mark attributes on the given endpoint
     274              :         // as having changing, but do NOT increase/alter any cluster data versions, as this happens when a bridged endpoint is
     275              :         // added or removed from a bridge and the cluster data is not changed during the process.
     276            0 :         emberAfEndpointChanged(path.mEndpointId, &change_listener);
     277              :     }
     278            0 : }
     279              : 
     280              : } // namespace app
     281              : } // namespace chip
        

Generated by: LCOV version 2.0-1