Matter SDK Coverage Report
Current view: top level - app - WriteHandler.cpp (source / functions) Coverage Total Hit
Test: SHA:0a260ffe149e0486eee9e451428453f0b12a3d3c Lines: 68.6 % 389 267
Test Date: 2025-06-06 07:10:05 Functions: 71.9 % 32 23

            Line data    Source code
       1              : /*
       2              :  *
       3              :  *    Copyright (c) 2021 Project CHIP Authors
       4              :  *    All rights reserved.
       5              :  *
       6              :  *    Licensed under the Apache License, Version 2.0 (the "License");
       7              :  *    you may not use this file except in compliance with the License.
       8              :  *    You may obtain a copy of the License at
       9              :  *
      10              :  *        http://www.apache.org/licenses/LICENSE-2.0
      11              :  *
      12              :  *    Unless required by applicable law or agreed to in writing, software
      13              :  *    distributed under the License is distributed on an "AS IS" BASIS,
      14              :  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      15              :  *    See the License for the specific language governing permissions and
      16              :  *    limitations under the License.
      17              :  */
      18              : 
      19              : #include <app/AppConfig.h>
      20              : #include <app/AttributeAccessInterfaceRegistry.h>
      21              : #include <app/AttributeValueDecoder.h>
      22              : #include <app/ConcreteAttributePath.h>
      23              : #include <app/GlobalAttributes.h>
      24              : #include <app/InteractionModelEngine.h>
      25              : #include <app/MessageDef/EventPathIB.h>
      26              : #include <app/MessageDef/StatusIB.h>
      27              : #include <app/StatusResponse.h>
      28              : #include <app/WriteHandler.h>
      29              : #include <app/data-model-provider/ActionReturnStatus.h>
      30              : #include <app/data-model-provider/MetadataLookup.h>
      31              : #include <app/data-model-provider/MetadataTypes.h>
      32              : #include <app/data-model-provider/OperationTypes.h>
      33              : #include <app/reporting/Engine.h>
      34              : #include <app/util/MatterCallbacks.h>
      35              : #include <credentials/GroupDataProvider.h>
      36              : #include <lib/core/CHIPError.h>
      37              : #include <lib/support/CodeUtils.h>
      38              : #include <lib/support/TypeTraits.h>
      39              : #include <lib/support/logging/TextOnlyLogging.h>
      40              : #include <messaging/ExchangeContext.h>
      41              : #include <protocols/interaction_model/StatusCode.h>
      42              : 
      43              : #include <optional>
      44              : 
      45              : namespace chip {
      46              : namespace app {
      47              : 
      48              : namespace {
      49              : 
      50              : using Protocols::InteractionModel::Status;
      51              : 
      52              : /// Wraps a EndpointIterator and ensures that `::Release()` is called
      53              : /// for the iterator (assuming it is non-null)
      54              : class AutoReleaseGroupEndpointIterator
      55              : {
      56              : public:
      57            0 :     explicit AutoReleaseGroupEndpointIterator(Credentials::GroupDataProvider::EndpointIterator * iterator) : mIterator(iterator) {}
      58            0 :     ~AutoReleaseGroupEndpointIterator()
      59              :     {
      60            0 :         if (mIterator != nullptr)
      61              :         {
      62            0 :             mIterator->Release();
      63              :         }
      64            0 :     }
      65              : 
      66            0 :     bool IsNull() const { return mIterator == nullptr; }
      67            0 :     bool Next(Credentials::GroupDataProvider::GroupEndpoint & item) { return mIterator->Next(item); }
      68              : 
      69              : private:
      70              :     Credentials::GroupDataProvider::EndpointIterator * mIterator;
      71              : };
      72              : 
      73              : } // namespace
      74              : 
      75              : using namespace Protocols::InteractionModel;
      76              : using Status = Protocols::InteractionModel::Status;
      77              : 
      78          960 : CHIP_ERROR WriteHandler::Init(DataModel::Provider * apProvider, WriteHandlerDelegate * apWriteHandlerDelegate)
      79              : {
      80          960 :     VerifyOrReturnError(!mExchangeCtx, CHIP_ERROR_INCORRECT_STATE);
      81          960 :     VerifyOrReturnError(apWriteHandlerDelegate, CHIP_ERROR_INVALID_ARGUMENT);
      82          960 :     VerifyOrReturnError(apProvider, CHIP_ERROR_INVALID_ARGUMENT);
      83          959 :     mDataModelProvider = apProvider;
      84              : 
      85          959 :     mDelegate = apWriteHandlerDelegate;
      86          959 :     MoveToState(State::Initialized);
      87              : 
      88          959 :     mACLCheckCache.ClearValue();
      89          959 :     mProcessingAttributePath.ClearValue();
      90              : 
      91          959 :     return CHIP_NO_ERROR;
      92              : }
      93              : 
      94          959 : void WriteHandler::Close()
      95              : {
      96          959 :     VerifyOrReturn(mState != State::Uninitialized);
      97              : 
      98              :     // DeliverFinalListWriteEnd will be a no-op if we have called
      99              :     // DeliverFinalListWriteEnd in success conditions, so passing false for
     100              :     // wasSuccessful here is safe: if it does anything, we were in fact not
     101              :     // successful.
     102          959 :     DeliverFinalListWriteEnd(false /* wasSuccessful */);
     103          959 :     mExchangeCtx.Release();
     104          959 :     mStateFlags.Clear(StateBits::kSuppressResponse);
     105          959 :     mDataModelProvider = nullptr;
     106          959 :     MoveToState(State::Uninitialized);
     107              : }
     108              : 
     109         1088 : std::optional<bool> WriteHandler::IsListAttributePath(const ConcreteAttributePath & path)
     110              : {
     111         1088 :     if (mDataModelProvider == nullptr)
     112              :     {
     113              : #if CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING
     114            0 :         ChipLogError(DataManagement, "Null data model while checking attribute properties.");
     115              : #endif
     116            0 :         return std::nullopt;
     117              :     }
     118              : 
     119         1088 :     DataModel::AttributeFinder finder(mDataModelProvider);
     120         1088 :     std::optional<DataModel::AttributeEntry> info = finder.Find(path);
     121              : 
     122         1088 :     if (!info.has_value())
     123              :     {
     124            0 :         return std::nullopt;
     125              :     }
     126              : 
     127         1088 :     return info->HasFlags(DataModel::AttributeQualityFlags::kListAttribute);
     128         1088 : }
     129              : 
     130         3871 : Status WriteHandler::HandleWriteRequestMessage(Messaging::ExchangeContext * apExchangeContext,
     131              :                                                System::PacketBufferHandle && aPayload, bool aIsTimedWrite)
     132              : {
     133         3871 :     System::PacketBufferHandle packet = System::PacketBufferHandle::New(chip::app::kMaxSecureSduLengthBytes);
     134         3871 :     VerifyOrReturnError(!packet.IsNull(), Status::Failure);
     135              : 
     136         3871 :     System::PacketBufferTLVWriter messageWriter;
     137         3871 :     messageWriter.Init(std::move(packet));
     138         3871 :     VerifyOrReturnError(mWriteResponseBuilder.Init(&messageWriter) == CHIP_NO_ERROR, Status::Failure);
     139              : 
     140         3871 :     mWriteResponseBuilder.CreateWriteResponses();
     141         3871 :     VerifyOrReturnError(mWriteResponseBuilder.GetError() == CHIP_NO_ERROR, Status::Failure);
     142              : 
     143         3871 :     Status status = ProcessWriteRequest(std::move(aPayload), aIsTimedWrite);
     144              : 
     145              :     // Do not send response on Group Write
     146         3871 :     if (status == Status::Success && !apExchangeContext->IsGroupExchangeContext())
     147              :     {
     148         3868 :         CHIP_ERROR err = SendWriteResponse(std::move(messageWriter));
     149         3868 :         if (err != CHIP_NO_ERROR)
     150              :         {
     151            0 :             status = Status::Failure;
     152              :         }
     153              :     }
     154              : 
     155         3871 :     return status;
     156         3871 : }
     157              : 
     158          959 : Status WriteHandler::OnWriteRequest(Messaging::ExchangeContext * apExchangeContext, System::PacketBufferHandle && aPayload,
     159              :                                     bool aIsTimedWrite)
     160              : {
     161              :     //
     162              :     // Let's take over further message processing on this exchange from the IM.
     163              :     // This is only relevant during chunked requests.
     164              :     //
     165          959 :     mExchangeCtx.Grab(apExchangeContext);
     166              : 
     167          959 :     Status status = HandleWriteRequestMessage(apExchangeContext, std::move(aPayload), aIsTimedWrite);
     168              : 
     169              :     // The write transaction will be alive only when the message was handled successfully and there are more chunks.
     170          959 :     if (!(status == Status::Success && mStateFlags.Has(StateBits::kHasMoreChunks)))
     171              :     {
     172           96 :         Close();
     173              :     }
     174              : 
     175          959 :     return status;
     176              : }
     177              : 
     178         2913 : CHIP_ERROR WriteHandler::OnMessageReceived(Messaging::ExchangeContext * apExchangeContext, const PayloadHeader & aPayloadHeader,
     179              :                                            System::PacketBufferHandle && aPayload)
     180              : {
     181         2913 :     CHIP_ERROR err = CHIP_NO_ERROR;
     182              : 
     183         2913 :     VerifyOrDieWithMsg(apExchangeContext == mExchangeCtx.Get(), DataManagement,
     184              :                        "Incoming exchange context should be same as the initial request.");
     185         2913 :     VerifyOrDieWithMsg(!apExchangeContext->IsGroupExchangeContext(), DataManagement,
     186              :                        "OnMessageReceived should not be called on GroupExchangeContext");
     187         2913 :     if (!aPayloadHeader.HasMessageType(Protocols::InteractionModel::MsgType::WriteRequest))
     188              :     {
     189            1 :         if (aPayloadHeader.HasMessageType(Protocols::InteractionModel::MsgType::StatusResponse))
     190              :         {
     191            0 :             CHIP_ERROR statusError = CHIP_NO_ERROR;
     192              :             // Parse the status response so we can log it properly.
     193            0 :             StatusResponse::ProcessStatusResponse(std::move(aPayload), statusError);
     194              :         }
     195            1 :         ChipLogDetail(DataManagement, "Unexpected message type %d", aPayloadHeader.GetMessageType());
     196            1 :         StatusResponse::Send(Status::InvalidAction, apExchangeContext, false /*aExpectResponse*/);
     197            1 :         Close();
     198            1 :         return CHIP_ERROR_INVALID_MESSAGE_TYPE;
     199              :     }
     200              : 
     201              :     Status status =
     202         2912 :         HandleWriteRequestMessage(apExchangeContext, std::move(aPayload), false /* chunked write should not be timed write */);
     203         2912 :     if (status == Status::Success)
     204              :     {
     205              :         // We have no more chunks, the write response has been sent in HandleWriteRequestMessage, so close directly.
     206         2912 :         if (!mStateFlags.Has(StateBits::kHasMoreChunks))
     207              :         {
     208          853 :             Close();
     209              :         }
     210              :     }
     211              :     else
     212              :     {
     213            0 :         err = StatusResponse::Send(status, apExchangeContext, false /*aExpectResponse*/);
     214            0 :         Close();
     215              :     }
     216         2912 :     return CHIP_NO_ERROR;
     217              : }
     218              : 
     219            8 : void WriteHandler::OnResponseTimeout(Messaging::ExchangeContext * apExchangeContext)
     220              : {
     221            8 :     ChipLogError(DataManagement, "Time out! failed to receive status response from Exchange: " ChipLogFormatExchange,
     222              :                  ChipLogValueExchange(apExchangeContext));
     223            8 :     Close();
     224            8 : }
     225              : 
     226         3868 : CHIP_ERROR WriteHandler::FinalizeMessage(System::PacketBufferTLVWriter && aMessageWriter, System::PacketBufferHandle & packet)
     227              : {
     228         3868 :     VerifyOrReturnError(mState == State::AddStatus, CHIP_ERROR_INCORRECT_STATE);
     229         3868 :     ReturnErrorOnFailure(mWriteResponseBuilder.GetWriteResponses().EndOfAttributeStatuses());
     230         3868 :     ReturnErrorOnFailure(mWriteResponseBuilder.EndOfWriteResponseMessage());
     231         3868 :     ReturnErrorOnFailure(aMessageWriter.Finalize(&packet));
     232         3868 :     return CHIP_NO_ERROR;
     233              : }
     234              : 
     235         3868 : CHIP_ERROR WriteHandler::SendWriteResponse(System::PacketBufferTLVWriter && aMessageWriter)
     236              : {
     237         3868 :     CHIP_ERROR err = CHIP_NO_ERROR;
     238         3868 :     System::PacketBufferHandle packet;
     239              : 
     240         3868 :     VerifyOrExit(mState == State::AddStatus, err = CHIP_ERROR_INCORRECT_STATE);
     241              : 
     242         3868 :     err = FinalizeMessage(std::move(aMessageWriter), packet);
     243         3868 :     SuccessOrExit(err);
     244              : 
     245         3868 :     VerifyOrExit(mExchangeCtx, err = CHIP_ERROR_INCORRECT_STATE);
     246         3868 :     mExchangeCtx->UseSuggestedResponseTimeout(app::kExpectedIMProcessingTime);
     247         7736 :     err = mExchangeCtx->SendMessage(Protocols::InteractionModel::MsgType::WriteResponse, std::move(packet),
     248         3868 :                                     mStateFlags.Has(StateBits::kHasMoreChunks) ? Messaging::SendMessageFlags::kExpectResponse
     249              :                                                                                : Messaging::SendMessageFlags::kNone);
     250         3868 :     SuccessOrExit(err);
     251              : 
     252         3868 :     MoveToState(State::Sending);
     253              : 
     254         3868 : exit:
     255         3868 :     return err;
     256         3868 : }
     257              : 
     258          937 : void WriteHandler::DeliverListWriteBegin(const ConcreteAttributePath & aPath)
     259              : {
     260          937 :     if (mDataModelProvider != nullptr)
     261              :     {
     262          937 :         mDataModelProvider->ListAttributeWriteNotification(aPath, DataModel::ListWriteOperation::kListWriteBegin);
     263              :     }
     264          937 : }
     265              : 
     266          942 : void WriteHandler::DeliverListWriteEnd(const ConcreteAttributePath & aPath, bool writeWasSuccessful)
     267              : {
     268          942 :     if (mDataModelProvider != nullptr)
     269              :     {
     270          942 :         mDataModelProvider->ListAttributeWriteNotification(aPath,
     271              :                                                            writeWasSuccessful ? DataModel::ListWriteOperation::kListWriteSuccess
     272              :                                                                               : DataModel::ListWriteOperation::kListWriteFailure);
     273              :     }
     274          942 : }
     275              : 
     276         1905 : void WriteHandler::DeliverFinalListWriteEnd(bool writeWasSuccessful)
     277              : {
     278         1905 :     if (mProcessingAttributePath.HasValue() && mStateFlags.Has(StateBits::kProcessingAttributeIsList))
     279              :     {
     280          934 :         DeliverListWriteEnd(mProcessingAttributePath.Value(), writeWasSuccessful);
     281              :     }
     282         1905 :     mProcessingAttributePath.ClearValue();
     283         1905 : }
     284              : 
     285            0 : CHIP_ERROR WriteHandler::DeliverFinalListWriteEndForGroupWrite(bool writeWasSuccessful)
     286              : {
     287            0 :     VerifyOrReturnError(mProcessingAttributePath.HasValue() && mStateFlags.Has(StateBits::kProcessingAttributeIsList),
     288              :                         CHIP_NO_ERROR);
     289              : 
     290            0 :     Credentials::GroupDataProvider::GroupEndpoint mapping;
     291            0 :     Credentials::GroupDataProvider * groupDataProvider = Credentials::GetGroupDataProvider();
     292              :     Credentials::GroupDataProvider::EndpointIterator * iterator;
     293              : 
     294            0 :     GroupId groupId         = mExchangeCtx->GetSessionHandle()->AsIncomingGroupSession()->GetGroupId();
     295            0 :     FabricIndex fabricIndex = GetAccessingFabricIndex();
     296              : 
     297            0 :     auto processingConcreteAttributePath = mProcessingAttributePath.Value();
     298            0 :     mProcessingAttributePath.ClearValue();
     299              : 
     300            0 :     iterator = groupDataProvider->IterateEndpoints(fabricIndex);
     301            0 :     VerifyOrReturnError(iterator != nullptr, CHIP_ERROR_NO_MEMORY);
     302              : 
     303            0 :     while (iterator->Next(mapping))
     304              :     {
     305            0 :         if (groupId != mapping.group_id)
     306              :         {
     307            0 :             continue;
     308              :         }
     309              : 
     310            0 :         processingConcreteAttributePath.mEndpointId = mapping.endpoint_id;
     311              : 
     312            0 :         VerifyOrReturnError(mDelegate, CHIP_ERROR_INCORRECT_STATE);
     313            0 :         if (!mDelegate->HasConflictWriteRequests(this, processingConcreteAttributePath))
     314              :         {
     315            0 :             DeliverListWriteEnd(processingConcreteAttributePath, writeWasSuccessful);
     316              :         }
     317              :     }
     318            0 :     iterator->Release();
     319            0 :     return CHIP_NO_ERROR;
     320              : }
     321              : namespace {
     322              : 
     323              : // To reduce the various use of previousProcessed.HasValue() && previousProcessed.Value() == nextAttribute to save code size.
     324        14497 : bool IsSameAttribute(const Optional<ConcreteAttributePath> & previousProcessed, const ConcreteDataAttributePath & nextAttribute)
     325              : {
     326        14497 :     return previousProcessed.HasValue() && previousProcessed.Value() == nextAttribute;
     327              : }
     328              : 
     329         5201 : bool ShouldReportListWriteEnd(const Optional<ConcreteAttributePath> & previousProcessed, bool previousProcessedAttributeIsList,
     330              :                               const ConcreteDataAttributePath & nextAttribute)
     331              : {
     332         5201 :     return previousProcessedAttributeIsList && !IsSameAttribute(previousProcessed, nextAttribute) && previousProcessed.HasValue();
     333              : }
     334              : 
     335         5201 : bool ShouldReportListWriteBegin(const Optional<ConcreteAttributePath> & previousProcessed, bool previousProcessedAttributeIsList,
     336              :                                 const ConcreteDataAttributePath & nextAttribute)
     337              : {
     338         5201 :     return !IsSameAttribute(previousProcessed, nextAttribute) && nextAttribute.IsListOperation();
     339              : }
     340              : 
     341              : } // namespace
     342              : 
     343         3868 : CHIP_ERROR WriteHandler::ProcessAttributeDataIBs(TLV::TLVReader & aAttributeDataIBsReader)
     344              : {
     345         3868 :     CHIP_ERROR err = CHIP_NO_ERROR;
     346              : 
     347         3868 :     VerifyOrReturnError(mExchangeCtx, CHIP_ERROR_INTERNAL);
     348         3868 :     const Access::SubjectDescriptor subjectDescriptor = mExchangeCtx->GetSessionHandle()->GetSubjectDescriptor();
     349              : 
     350         9082 :     while (CHIP_NO_ERROR == (err = aAttributeDataIBsReader.Next()))
     351              :     {
     352         5214 :         chip::TLV::TLVReader dataReader;
     353         5214 :         AttributeDataIB::Parser element;
     354         5214 :         AttributePathIB::Parser attributePath;
     355         5214 :         ConcreteDataAttributePath dataAttributePath;
     356         5214 :         TLV::TLVReader reader = aAttributeDataIBsReader;
     357              : 
     358         5214 :         err = element.Init(reader);
     359         5214 :         SuccessOrExit(err);
     360              : 
     361         5214 :         err = element.GetPath(&attributePath);
     362         5214 :         SuccessOrExit(err);
     363              : 
     364         5214 :         err = attributePath.GetConcreteAttributePath(dataAttributePath);
     365         5214 :         SuccessOrExit(err);
     366              : 
     367         5214 :         err = element.GetData(&dataReader);
     368         5214 :         SuccessOrExit(err);
     369              : 
     370         5214 :         if (!dataAttributePath.IsListOperation() && IsListAttributePath(dataAttributePath).value_or(false))
     371              :         {
     372         1065 :             dataAttributePath.mListOp = ConcreteDataAttributePath::ListOperation::ReplaceAll;
     373              :         }
     374              : 
     375         5214 :         VerifyOrExit(mDelegate, err = CHIP_ERROR_INCORRECT_STATE);
     376        14540 :         if (mDelegate->HasConflictWriteRequests(this, dataAttributePath) ||
     377              :             // Per chunking protocol, we are processing the list entries, but the initial empty list is not processed, so we reject
     378              :             // it with Busy status code.
     379         9326 :             (dataAttributePath.IsListItemOperation() && !IsSameAttribute(mProcessingAttributePath, dataAttributePath)))
     380              :         {
     381           13 :             err = AddStatusInternal(dataAttributePath, StatusIB(Status::Busy));
     382           13 :             continue;
     383              :         }
     384              : 
     385         5201 :         if (ShouldReportListWriteEnd(mProcessingAttributePath, mStateFlags.Has(StateBits::kProcessingAttributeIsList),
     386              :                                      dataAttributePath))
     387              :         {
     388            8 :             DeliverListWriteEnd(mProcessingAttributePath.Value(), mStateFlags.Has(StateBits::kAttributeWriteSuccessful));
     389              :         }
     390              : 
     391         5201 :         if (ShouldReportListWriteBegin(mProcessingAttributePath, mStateFlags.Has(StateBits::kProcessingAttributeIsList),
     392              :                                        dataAttributePath))
     393              :         {
     394          937 :             DeliverListWriteBegin(dataAttributePath);
     395          937 :             mStateFlags.Set(StateBits::kAttributeWriteSuccessful);
     396              :         }
     397              : 
     398         5201 :         mStateFlags.Set(StateBits::kProcessingAttributeIsList, dataAttributePath.IsListOperation());
     399         5201 :         mProcessingAttributePath.SetValue(dataAttributePath);
     400              : 
     401         5201 :         DataModelCallbacks::GetInstance()->AttributeOperation(DataModelCallbacks::OperationType::Write,
     402              :                                                               DataModelCallbacks::OperationOrder::Pre, dataAttributePath);
     403              : 
     404         5201 :         TLV::TLVWriter backup;
     405         5201 :         DataVersion version = 0;
     406         5201 :         mWriteResponseBuilder.GetWriteResponses().Checkpoint(backup);
     407         5201 :         err = element.GetDataVersion(&version);
     408         5201 :         if (CHIP_NO_ERROR == err)
     409              :         {
     410           12 :             dataAttributePath.mDataVersion.SetValue(version);
     411              :         }
     412         5189 :         else if (CHIP_END_OF_TLV == err)
     413              :         {
     414         5189 :             err = CHIP_NO_ERROR;
     415              :         }
     416         5201 :         SuccessOrExit(err);
     417         5201 :         err = WriteClusterData(subjectDescriptor, dataAttributePath, dataReader);
     418         5201 :         if (err != CHIP_NO_ERROR)
     419              :         {
     420            0 :             mWriteResponseBuilder.GetWriteResponses().Rollback(backup);
     421            0 :             err = AddStatusInternal(dataAttributePath, StatusIB(err));
     422              :         }
     423              : 
     424         5201 :         DataModelCallbacks::GetInstance()->AttributeOperation(DataModelCallbacks::OperationType::Write,
     425              :                                                               DataModelCallbacks::OperationOrder::Post, dataAttributePath);
     426         5201 :         SuccessOrExit(err);
     427              :     }
     428              : 
     429         3868 :     if (CHIP_END_OF_TLV == err)
     430              :     {
     431         3868 :         err = CHIP_NO_ERROR;
     432              :     }
     433              : 
     434         3868 :     SuccessOrExit(err);
     435              : 
     436         3868 :     if (!mStateFlags.Has(StateBits::kHasMoreChunks))
     437              :     {
     438          946 :         DeliverFinalListWriteEnd(mStateFlags.Has(StateBits::kAttributeWriteSuccessful));
     439              :     }
     440              : 
     441         2922 : exit:
     442         3868 :     return err;
     443              : }
     444              : 
     445            0 : CHIP_ERROR WriteHandler::ProcessGroupAttributeDataIBs(TLV::TLVReader & aAttributeDataIBsReader)
     446              : {
     447            0 :     CHIP_ERROR err = CHIP_NO_ERROR;
     448              : 
     449            0 :     VerifyOrReturnError(mExchangeCtx, CHIP_ERROR_INTERNAL);
     450              :     const Access::SubjectDescriptor subjectDescriptor =
     451            0 :         mExchangeCtx->GetSessionHandle()->AsIncomingGroupSession()->GetSubjectDescriptor();
     452              : 
     453            0 :     GroupId groupId    = mExchangeCtx->GetSessionHandle()->AsIncomingGroupSession()->GetGroupId();
     454            0 :     FabricIndex fabric = GetAccessingFabricIndex();
     455              : 
     456            0 :     while (CHIP_NO_ERROR == (err = aAttributeDataIBsReader.Next()))
     457              :     {
     458            0 :         chip::TLV::TLVReader dataReader;
     459            0 :         AttributeDataIB::Parser element;
     460            0 :         AttributePathIB::Parser attributePath;
     461            0 :         ConcreteDataAttributePath dataAttributePath;
     462            0 :         TLV::TLVReader reader = aAttributeDataIBsReader;
     463              : 
     464            0 :         err = element.Init(reader);
     465            0 :         SuccessOrExit(err);
     466              : 
     467            0 :         err = element.GetPath(&attributePath);
     468            0 :         SuccessOrExit(err);
     469              : 
     470            0 :         err = attributePath.GetGroupAttributePath(dataAttributePath);
     471            0 :         SuccessOrExit(err);
     472              : 
     473            0 :         err = element.GetData(&dataReader);
     474            0 :         SuccessOrExit(err);
     475              : 
     476            0 :         if (!dataAttributePath.IsListOperation() && dataReader.GetType() == TLV::TLVType::kTLVType_Array)
     477              :         {
     478            0 :             dataAttributePath.mListOp = ConcreteDataAttributePath::ListOperation::ReplaceAll;
     479              :         }
     480              : 
     481            0 :         ChipLogDetail(DataManagement,
     482              :                       "Received group attribute write for Group=%u Cluster=" ChipLogFormatMEI " attribute=" ChipLogFormatMEI,
     483              :                       groupId, ChipLogValueMEI(dataAttributePath.mClusterId), ChipLogValueMEI(dataAttributePath.mAttributeId));
     484              : 
     485            0 :         AutoReleaseGroupEndpointIterator iterator(Credentials::GetGroupDataProvider()->IterateEndpoints(fabric));
     486            0 :         VerifyOrExit(!iterator.IsNull(), err = CHIP_ERROR_NO_MEMORY);
     487              : 
     488            0 :         bool shouldReportListWriteEnd = ShouldReportListWriteEnd(
     489            0 :             mProcessingAttributePath, mStateFlags.Has(StateBits::kProcessingAttributeIsList), dataAttributePath);
     490            0 :         bool shouldReportListWriteBegin = false; // This will be set below.
     491              : 
     492            0 :         std::optional<bool> isListAttribute = std::nullopt;
     493              : 
     494            0 :         Credentials::GroupDataProvider::GroupEndpoint mapping;
     495            0 :         while (iterator.Next(mapping))
     496              :         {
     497            0 :             if (groupId != mapping.group_id)
     498              :             {
     499            0 :                 continue;
     500              :             }
     501              : 
     502            0 :             dataAttributePath.mEndpointId = mapping.endpoint_id;
     503              : 
     504              :             // Try to get the metadata from for the attribute from one of the expanded endpoints (it doesn't really matter which
     505              :             // endpoint we pick, as long as it's valid) and update the path info according to it and recheck if we need to report
     506              :             // list write begin.
     507            0 :             if (!isListAttribute.has_value())
     508              :             {
     509            0 :                 isListAttribute             = IsListAttributePath(dataAttributePath);
     510            0 :                 bool currentAttributeIsList = isListAttribute.value_or(false);
     511              : 
     512            0 :                 if (!dataAttributePath.IsListOperation() && currentAttributeIsList)
     513              :                 {
     514            0 :                     dataAttributePath.mListOp = ConcreteDataAttributePath::ListOperation::ReplaceAll;
     515              :                 }
     516              :                 ConcreteDataAttributePath pathForCheckingListWriteBegin(kInvalidEndpointId, dataAttributePath.mClusterId,
     517            0 :                                                                         dataAttributePath.mEndpointId, dataAttributePath.mListOp,
     518            0 :                                                                         dataAttributePath.mListIndex);
     519              :                 shouldReportListWriteBegin =
     520            0 :                     ShouldReportListWriteBegin(mProcessingAttributePath, mStateFlags.Has(StateBits::kProcessingAttributeIsList),
     521              :                                                pathForCheckingListWriteBegin);
     522              :             }
     523              : 
     524            0 :             if (shouldReportListWriteEnd)
     525              :             {
     526            0 :                 auto processingConcreteAttributePath        = mProcessingAttributePath.Value();
     527            0 :                 processingConcreteAttributePath.mEndpointId = mapping.endpoint_id;
     528            0 :                 VerifyOrExit(mDelegate, err = CHIP_ERROR_INCORRECT_STATE);
     529            0 :                 if (mDelegate->HasConflictWriteRequests(this, processingConcreteAttributePath))
     530              :                 {
     531            0 :                     DeliverListWriteEnd(processingConcreteAttributePath, true /* writeWasSuccessful */);
     532              :                 }
     533              :             }
     534              : 
     535            0 :             VerifyOrExit(mDelegate, err = CHIP_ERROR_INCORRECT_STATE);
     536            0 :             if (mDelegate->HasConflictWriteRequests(this, dataAttributePath))
     537              :             {
     538            0 :                 ChipLogDetail(DataManagement,
     539              :                               "Writing attribute endpoint=%u Cluster=" ChipLogFormatMEI " attribute=" ChipLogFormatMEI
     540              :                               " is conflict with other write transactions.",
     541              :                               mapping.endpoint_id, ChipLogValueMEI(dataAttributePath.mClusterId),
     542              :                               ChipLogValueMEI(dataAttributePath.mAttributeId));
     543            0 :                 continue;
     544              :             }
     545              : 
     546            0 :             if (shouldReportListWriteBegin)
     547              :             {
     548            0 :                 DeliverListWriteBegin(dataAttributePath);
     549              :             }
     550              : 
     551            0 :             ChipLogDetail(DataManagement,
     552              :                           "Processing group attribute write for endpoint=%u Cluster=" ChipLogFormatMEI
     553              :                           " attribute=" ChipLogFormatMEI,
     554              :                           mapping.endpoint_id, ChipLogValueMEI(dataAttributePath.mClusterId),
     555              :                           ChipLogValueMEI(dataAttributePath.mAttributeId));
     556              : 
     557            0 :             chip::TLV::TLVReader tmpDataReader(dataReader);
     558              : 
     559            0 :             DataModelCallbacks::GetInstance()->AttributeOperation(DataModelCallbacks::OperationType::Write,
     560              :                                                                   DataModelCallbacks::OperationOrder::Pre, dataAttributePath);
     561            0 :             err = WriteClusterData(subjectDescriptor, dataAttributePath, tmpDataReader);
     562            0 :             if (err != CHIP_NO_ERROR)
     563              :             {
     564            0 :                 ChipLogError(DataManagement,
     565              :                              "WriteClusterData Endpoint=%u Cluster=" ChipLogFormatMEI " Attribute =" ChipLogFormatMEI
     566              :                              " failed: %" CHIP_ERROR_FORMAT,
     567              :                              mapping.endpoint_id, ChipLogValueMEI(dataAttributePath.mClusterId),
     568              :                              ChipLogValueMEI(dataAttributePath.mAttributeId), err.Format());
     569              :             }
     570            0 :             DataModelCallbacks::GetInstance()->AttributeOperation(DataModelCallbacks::OperationType::Write,
     571              :                                                                   DataModelCallbacks::OperationOrder::Post, dataAttributePath);
     572              :         }
     573              : 
     574            0 :         dataAttributePath.mEndpointId = kInvalidEndpointId;
     575            0 :         mStateFlags.Set(StateBits::kProcessingAttributeIsList, dataAttributePath.IsListOperation());
     576            0 :         mProcessingAttributePath.SetValue(dataAttributePath);
     577            0 :     }
     578              : 
     579            0 :     if (CHIP_END_OF_TLV == err)
     580              :     {
     581            0 :         err = CHIP_NO_ERROR;
     582              :     }
     583              : 
     584            0 :     err = DeliverFinalListWriteEndForGroupWrite(true);
     585              : 
     586            0 : exit:
     587              :     // The DeliverFinalListWriteEndForGroupWrite above will deliver the successful state of the list write and clear the
     588              :     // mProcessingAttributePath making the following call no-op. So we call it again after the exit label to deliver a failure state
     589              :     // to the clusters. Ignore the error code since we need to deliver other more important failures.
     590            0 :     DeliverFinalListWriteEndForGroupWrite(false);
     591            0 :     return err;
     592              : }
     593              : 
     594         3871 : Status WriteHandler::ProcessWriteRequest(System::PacketBufferHandle && aPayload, bool aIsTimedWrite)
     595              : {
     596         3871 :     CHIP_ERROR err = CHIP_NO_ERROR;
     597         3871 :     System::PacketBufferTLVReader reader;
     598              : 
     599         3871 :     WriteRequestMessage::Parser writeRequestParser;
     600         3871 :     AttributeDataIBs::Parser AttributeDataIBsParser;
     601         3871 :     TLV::TLVReader AttributeDataIBsReader;
     602              :     // Default to InvalidAction for our status; that's what we want if any of
     603              :     // the parsing of our overall structure or paths fails.  Once we have a
     604              :     // successfully parsed path, the only way we will get a failure return is if
     605              :     // our path handling fails to AddStatus on us.
     606              :     //
     607              :     // TODO: That's not technically InvalidAction, and we should probably make
     608              :     // our callees hand out Status as well.
     609         3871 :     Status status = Status::InvalidAction;
     610              : 
     611         3871 :     mLastSuccessfullyWrittenPath = std::nullopt;
     612              : 
     613         3871 :     reader.Init(std::move(aPayload));
     614              : 
     615         3871 :     err = writeRequestParser.Init(reader);
     616         3871 :     SuccessOrExit(err);
     617              : 
     618              : #if CHIP_CONFIG_IM_PRETTY_PRINT
     619         3870 :     writeRequestParser.PrettyPrint();
     620              : #endif // CHIP_CONFIG_IM_PRETTY_PRINT
     621              :     bool boolValue;
     622              : 
     623         3870 :     boolValue = mStateFlags.Has(StateBits::kSuppressResponse);
     624         3870 :     err       = writeRequestParser.GetSuppressResponse(&boolValue);
     625         3870 :     if (err == CHIP_END_OF_TLV)
     626              :     {
     627            4 :         err = CHIP_NO_ERROR;
     628              :     }
     629         3870 :     SuccessOrExit(err);
     630         3870 :     mStateFlags.Set(StateBits::kSuppressResponse, boolValue);
     631              : 
     632         3870 :     boolValue = mStateFlags.Has(StateBits::kIsTimedRequest);
     633         3870 :     err       = writeRequestParser.GetTimedRequest(&boolValue);
     634         3870 :     SuccessOrExit(err);
     635         3870 :     mStateFlags.Set(StateBits::kIsTimedRequest, boolValue);
     636              : 
     637         3870 :     boolValue = mStateFlags.Has(StateBits::kHasMoreChunks);
     638         3870 :     err       = writeRequestParser.GetMoreChunkedMessages(&boolValue);
     639         3870 :     if (err == CHIP_ERROR_END_OF_TLV)
     640              :     {
     641            4 :         err = CHIP_NO_ERROR;
     642              :     }
     643         3870 :     SuccessOrExit(err);
     644         3870 :     mStateFlags.Set(StateBits::kHasMoreChunks, boolValue);
     645              : 
     646         9714 :     if (mStateFlags.Has(StateBits::kHasMoreChunks) &&
     647         5844 :         (mExchangeCtx->IsGroupExchangeContext() || mStateFlags.Has(StateBits::kIsTimedRequest)))
     648              :     {
     649              :         // Sanity check: group exchange context should only have one chunk.
     650              :         // Also, timed requests should not have more than one chunk.
     651            0 :         ExitNow(err = CHIP_ERROR_INVALID_MESSAGE_TYPE);
     652              :     }
     653              : 
     654         3870 :     err = writeRequestParser.GetWriteRequests(&AttributeDataIBsParser);
     655         3870 :     SuccessOrExit(err);
     656              : 
     657         3870 :     if (mStateFlags.Has(StateBits::kIsTimedRequest) != aIsTimedWrite)
     658              :     {
     659              :         // The message thinks it should be part of a timed interaction but it's
     660              :         // not, or vice versa.
     661            2 :         status = Status::TimedRequestMismatch;
     662            2 :         goto exit;
     663              :     }
     664              : 
     665         3868 :     AttributeDataIBsParser.GetReader(&AttributeDataIBsReader);
     666              : 
     667         3868 :     if (mExchangeCtx->IsGroupExchangeContext())
     668              :     {
     669            0 :         err = ProcessGroupAttributeDataIBs(AttributeDataIBsReader);
     670              :     }
     671              :     else
     672              :     {
     673         3868 :         err = ProcessAttributeDataIBs(AttributeDataIBsReader);
     674              :     }
     675         3868 :     SuccessOrExit(err);
     676         3868 :     SuccessOrExit(err = writeRequestParser.ExitContainer());
     677              : 
     678         3868 :     if (err == CHIP_NO_ERROR)
     679              :     {
     680         3868 :         status = Status::Success;
     681              :     }
     682              : 
     683            0 : exit:
     684         3871 :     if (err != CHIP_NO_ERROR)
     685              :     {
     686            1 :         ChipLogError(DataManagement, "Failed to process write request: %" CHIP_ERROR_FORMAT, err.Format());
     687              :     }
     688         7742 :     return status;
     689         3871 : }
     690              : 
     691            0 : CHIP_ERROR WriteHandler::AddStatus(const ConcreteDataAttributePath & aPath,
     692              :                                    const Protocols::InteractionModel::ClusterStatusCode & aStatus)
     693              : {
     694            0 :     return AddStatusInternal(aPath, StatusIB{ aStatus });
     695              : }
     696              : 
     697            0 : CHIP_ERROR WriteHandler::AddClusterSpecificSuccess(const ConcreteDataAttributePath & aPath, ClusterStatus aClusterStatus)
     698              : {
     699            0 :     return AddStatus(aPath, Protocols::InteractionModel::ClusterStatusCode::ClusterSpecificSuccess(aClusterStatus));
     700              : }
     701              : 
     702            0 : CHIP_ERROR WriteHandler::AddClusterSpecificFailure(const ConcreteDataAttributePath & aPath, ClusterStatus aClusterStatus)
     703              : {
     704            0 :     return AddStatus(aPath, Protocols::InteractionModel::ClusterStatusCode::ClusterSpecificFailure(aClusterStatus));
     705              : }
     706              : 
     707         5214 : CHIP_ERROR WriteHandler::AddStatusInternal(const ConcreteDataAttributePath & aPath, const StatusIB & aStatus)
     708              : {
     709         5214 :     AttributeStatusIBs::Builder & writeResponses   = mWriteResponseBuilder.GetWriteResponses();
     710         5214 :     AttributeStatusIB::Builder & attributeStatusIB = writeResponses.CreateAttributeStatus();
     711              : 
     712         5214 :     if (!aStatus.IsSuccess())
     713              :     {
     714           43 :         mStateFlags.Clear(StateBits::kAttributeWriteSuccessful);
     715              :     }
     716              : 
     717         5214 :     ReturnErrorOnFailure(writeResponses.GetError());
     718              : 
     719         5214 :     AttributePathIB::Builder & path = attributeStatusIB.CreatePath();
     720         5214 :     ReturnErrorOnFailure(attributeStatusIB.GetError());
     721         5214 :     ReturnErrorOnFailure(path.Encode(aPath));
     722              : 
     723         5214 :     StatusIB::Builder & statusIBBuilder = attributeStatusIB.CreateErrorStatus();
     724         5214 :     ReturnErrorOnFailure(attributeStatusIB.GetError());
     725         5214 :     statusIBBuilder.EncodeStatusIB(aStatus);
     726         5214 :     ReturnErrorOnFailure(statusIBBuilder.GetError());
     727         5214 :     ReturnErrorOnFailure(attributeStatusIB.EndOfAttributeStatusIB());
     728              : 
     729         5214 :     MoveToState(State::AddStatus);
     730         5214 :     return CHIP_NO_ERROR;
     731              : }
     732              : 
     733            1 : FabricIndex WriteHandler::GetAccessingFabricIndex() const
     734              : {
     735            1 :     return mExchangeCtx->GetSessionHandle()->GetFabricIndex();
     736              : }
     737              : 
     738        11000 : const char * WriteHandler::GetStateStr() const
     739              : {
     740              : #if CHIP_DETAIL_LOGGING
     741        11000 :     switch (mState)
     742              :     {
     743          959 :     case State::Uninitialized:
     744          959 :         return "Uninitialized";
     745              : 
     746          959 :     case State::Initialized:
     747          959 :         return "Initialized";
     748              : 
     749         5214 :     case State::AddStatus:
     750         5214 :         return "AddStatus";
     751         3868 :     case State::Sending:
     752         3868 :         return "Sending";
     753              :     }
     754              : #endif // CHIP_DETAIL_LOGGING
     755            0 :     return "N/A";
     756              : }
     757              : 
     758        11000 : void WriteHandler::MoveToState(const State aTargetState)
     759              : {
     760        11000 :     mState = aTargetState;
     761        11000 :     ChipLogDetail(DataManagement, "IM WH moving to [%s]", GetStateStr());
     762        11000 : }
     763              : 
     764         5201 : DataModel::ActionReturnStatus WriteHandler::CheckWriteAllowed(const Access::SubjectDescriptor & aSubject,
     765              :                                                               const ConcreteAttributePath & aPath)
     766              : {
     767              :     // TODO: ordering is to check writability/existence BEFORE ACL and this seems wrong, however
     768              :     //       existing unit tests (TC_AcessChecker.py) validate that we get UnsupportedWrite instead of UnsupportedAccess
     769              :     //
     770              :     //       This should likely be fixed in spec (probably already fixed by
     771              :     //       https://github.com/CHIP-Specifications/connectedhomeip-spec/pull/9024)
     772              :     //       and tests and implementation
     773              :     //
     774              :     //       Open issue that needs fixing: https://github.com/project-chip/connectedhomeip/issues/33735
     775              : 
     776         5201 :     DataModel::AttributeFinder finder(mDataModelProvider);
     777              : 
     778         5201 :     std::optional<DataModel::AttributeEntry> attributeEntry = finder.Find(aPath);
     779              : 
     780              :     // if path is not valid, return a spec-compliant return code.
     781         5201 :     if (!attributeEntry.has_value())
     782              :     {
     783              :         // Global lists are not in metadata and not writable. Return the correct error code according to the spec
     784              :         Status attributeErrorStatus =
     785            0 :             IsSupportedGlobalAttributeNotInMetadata(aPath.mAttributeId) ? Status::UnsupportedWrite : Status::UnsupportedAttribute;
     786              : 
     787            0 :         return DataModel::ValidateClusterPath(mDataModelProvider, aPath, attributeErrorStatus);
     788              :     }
     789              : 
     790              :     // Allow writes on writable attributes only
     791         5201 :     VerifyOrReturnValue(attributeEntry->GetWritePrivilege().has_value(), Status::UnsupportedWrite);
     792              : 
     793         5201 :     bool checkAcl = true;
     794         5201 :     if (mLastSuccessfullyWrittenPath.has_value())
     795              :     {
     796              :         // only validate ACL if path has changed
     797              :         //
     798              :         // Note that this is NOT operator==: we could do `checkAcl == (aPath != *mLastSuccessfullyWrittenPath)`
     799              :         // however that seems to use more flash.
     800         1324 :         if ((aPath.mEndpointId == mLastSuccessfullyWrittenPath->mEndpointId) &&
     801         2648 :             (aPath.mClusterId == mLastSuccessfullyWrittenPath->mClusterId) &&
     802         1324 :             (aPath.mAttributeId == mLastSuccessfullyWrittenPath->mAttributeId))
     803              :         {
     804         1316 :             checkAcl = false;
     805              :         }
     806              :     }
     807              : 
     808         5201 :     if (checkAcl)
     809              :     {
     810         3885 :         Access::RequestPath requestPath{ .cluster     = aPath.mClusterId,
     811         3885 :                                          .endpoint    = aPath.mEndpointId,
     812              :                                          .requestType = Access::RequestType::kAttributeWriteRequest,
     813         3885 :                                          .entityId    = aPath.mAttributeId };
     814              : 
     815              :         // NOTE: we know that attributeEntry has a GetWriteProvilege based on the check above.
     816              :         //       so we just directly reference it.
     817         3885 :         CHIP_ERROR err = Access::GetAccessControl().Check(aSubject, requestPath, *attributeEntry->GetWritePrivilege());
     818              : 
     819         3885 :         if (err != CHIP_NO_ERROR)
     820              :         {
     821            0 :             VerifyOrReturnValue(err != CHIP_ERROR_ACCESS_DENIED, Status::UnsupportedAccess);
     822            0 :             VerifyOrReturnValue(err != CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL, Status::AccessRestricted);
     823              : 
     824            0 :             return err;
     825              :         }
     826              :     }
     827              : 
     828              :     // validate that timed write is enforced
     829         5201 :     VerifyOrReturnValue(IsTimedWrite() || !attributeEntry->HasFlags(DataModel::AttributeQualityFlags::kTimed),
     830              :                         Status::NeedsTimedInteraction);
     831              : 
     832         5201 :     return Status::Success;
     833         5201 : }
     834              : 
     835         5201 : CHIP_ERROR WriteHandler::WriteClusterData(const Access::SubjectDescriptor & aSubject, const ConcreteDataAttributePath & aPath,
     836              :                                           TLV::TLVReader & aData)
     837              : {
     838              :     // Writes do not have a checked-path. If data model interface is enabled (both checked and only version)
     839              :     // the write is done via the DataModel interface
     840         5201 :     VerifyOrReturnError(mDataModelProvider != nullptr, CHIP_ERROR_INCORRECT_STATE);
     841              : 
     842         5201 :     ChipLogDetail(DataManagement, "Writing attribute: Cluster=" ChipLogFormatMEI " Endpoint=0x%x AttributeId=" ChipLogFormatMEI,
     843              :                   ChipLogValueMEI(aPath.mClusterId), aPath.mEndpointId, ChipLogValueMEI(aPath.mAttributeId));
     844              : 
     845         5201 :     DataModel::ActionReturnStatus status = CheckWriteAllowed(aSubject, aPath);
     846         5201 :     if (status.IsSuccess())
     847              :     {
     848         5201 :         DataModel::WriteAttributeRequest request;
     849              : 
     850         5201 :         request.path              = aPath;
     851         5201 :         request.subjectDescriptor = &aSubject;
     852         5201 :         request.writeFlags.Set(DataModel::WriteFlags::kTimed, IsTimedWrite());
     853              : 
     854         5201 :         AttributeValueDecoder decoder(aData, aSubject);
     855         5201 :         status = mDataModelProvider->WriteAttribute(request, decoder);
     856              :     }
     857              : 
     858         5201 :     mLastSuccessfullyWrittenPath = status.IsSuccess() ? std::make_optional(aPath) : std::nullopt;
     859              : 
     860         5201 :     return AddStatusInternal(aPath, StatusIB(status.GetStatusCode()));
     861              : }
     862              : 
     863              : } // namespace app
     864              : } // namespace chip
        

Generated by: LCOV version 2.0-1