| /* |
| * |
| * Copyright (c) 2021 Project CHIP Authors |
| * All rights reserved. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include <app/AppConfig.h> |
| #include <app/AttributeAccessInterfaceRegistry.h> |
| #include <app/AttributeValueDecoder.h> |
| #include <app/InteractionModelEngine.h> |
| #include <app/MessageDef/EventPathIB.h> |
| #include <app/MessageDef/StatusIB.h> |
| #include <app/StatusResponse.h> |
| #include <app/WriteHandler.h> |
| #include <app/data-model-provider/MetadataTypes.h> |
| #include <app/data-model-provider/OperationTypes.h> |
| #include <app/reporting/Engine.h> |
| #include <app/util/MatterCallbacks.h> |
| #include <app/util/ember-compatibility-functions.h> |
| #include <credentials/GroupDataProvider.h> |
| #include <lib/core/CHIPError.h> |
| #include <lib/support/CodeUtils.h> |
| #include <lib/support/TypeTraits.h> |
| #include <lib/support/logging/TextOnlyLogging.h> |
| #include <messaging/ExchangeContext.h> |
| #include <protocols/interaction_model/StatusCode.h> |
| |
| #include <optional> |
| |
| namespace chip { |
| namespace app { |
| |
| namespace { |
| |
| /// Wraps a EndpointIterator and ensures that `::Release()` is called |
| /// for the iterator (assuming it is non-null) |
| class AutoReleaseGroupEndpointIterator |
| { |
| public: |
| explicit AutoReleaseGroupEndpointIterator(Credentials::GroupDataProvider::EndpointIterator * iterator) : mIterator(iterator) {} |
| ~AutoReleaseGroupEndpointIterator() |
| { |
| if (mIterator != nullptr) |
| { |
| mIterator->Release(); |
| } |
| } |
| |
| bool IsNull() const { return mIterator == nullptr; } |
| bool Next(Credentials::GroupDataProvider::GroupEndpoint & item) { return mIterator->Next(item); } |
| |
| private: |
| Credentials::GroupDataProvider::EndpointIterator * mIterator; |
| }; |
| |
| } // namespace |
| |
| using namespace Protocols::InteractionModel; |
| using Status = Protocols::InteractionModel::Status; |
| |
| CHIP_ERROR WriteHandler::Init(DataModel::Provider * apProvider, WriteHandlerDelegate * apWriteHandlerDelegate) |
| { |
| VerifyOrReturnError(!mExchangeCtx, CHIP_ERROR_INCORRECT_STATE); |
| VerifyOrReturnError(apWriteHandlerDelegate, CHIP_ERROR_INVALID_ARGUMENT); |
| #if CHIP_CONFIG_USE_DATA_MODEL_INTERFACE |
| VerifyOrReturnError(apProvider, CHIP_ERROR_INVALID_ARGUMENT); |
| mDataModelProvider = apProvider; |
| #endif // CHIP_CONFIG_USE_DATA_MODEL_INTERFACE |
| |
| mDelegate = apWriteHandlerDelegate; |
| MoveToState(State::Initialized); |
| |
| mACLCheckCache.ClearValue(); |
| mProcessingAttributePath.ClearValue(); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| void WriteHandler::Close() |
| { |
| VerifyOrReturn(mState != State::Uninitialized); |
| |
| // DeliverFinalListWriteEnd will be a no-op if we have called |
| // DeliverFinalListWriteEnd in success conditions, so passing false for |
| // wasSuccessful here is safe: if it does anything, we were in fact not |
| // successful. |
| DeliverFinalListWriteEnd(false /* wasSuccessful */); |
| mExchangeCtx.Release(); |
| mStateFlags.Clear(StateBits::kSuppressResponse); |
| #if CHIP_CONFIG_USE_DATA_MODEL_INTERFACE |
| mDataModelProvider = nullptr; |
| #endif // CHIP_CONFIG_USE_DATA_MODEL_INTERFACE |
| MoveToState(State::Uninitialized); |
| } |
| |
| std::optional<bool> WriteHandler::IsListAttributePath(const ConcreteAttributePath & path) |
| { |
| #if CHIP_CONFIG_USE_DATA_MODEL_INTERFACE |
| VerifyOrReturnValue(mDataModelProvider != nullptr, std::nullopt, |
| ChipLogError(DataManagement, "Null data model while checking attribute properties.")); |
| |
| auto info = mDataModelProvider->GetAttributeInfo(path); |
| if (!info.has_value()) |
| { |
| return std::nullopt; |
| } |
| |
| return info->flags.Has(DataModel::AttributeQualityFlags::kListAttribute); |
| #else |
| constexpr uint8_t kListAttributeType = 0x48; |
| const auto attributeMetadata = GetAttributeMetadata(path); |
| if (attributeMetadata == nullptr) |
| { |
| return std::nullopt; |
| } |
| return (attributeMetadata->attributeType == kListAttributeType); |
| #endif |
| } |
| |
| Status WriteHandler::HandleWriteRequestMessage(Messaging::ExchangeContext * apExchangeContext, |
| System::PacketBufferHandle && aPayload, bool aIsTimedWrite) |
| { |
| System::PacketBufferHandle packet = System::PacketBufferHandle::New(chip::app::kMaxSecureSduLengthBytes); |
| VerifyOrReturnError(!packet.IsNull(), Status::Failure); |
| |
| System::PacketBufferTLVWriter messageWriter; |
| messageWriter.Init(std::move(packet)); |
| VerifyOrReturnError(mWriteResponseBuilder.Init(&messageWriter) == CHIP_NO_ERROR, Status::Failure); |
| |
| mWriteResponseBuilder.CreateWriteResponses(); |
| VerifyOrReturnError(mWriteResponseBuilder.GetError() == CHIP_NO_ERROR, Status::Failure); |
| |
| Status status = ProcessWriteRequest(std::move(aPayload), aIsTimedWrite); |
| |
| // Do not send response on Group Write |
| if (status == Status::Success && !apExchangeContext->IsGroupExchangeContext()) |
| { |
| CHIP_ERROR err = SendWriteResponse(std::move(messageWriter)); |
| if (err != CHIP_NO_ERROR) |
| { |
| status = Status::Failure; |
| } |
| } |
| |
| return status; |
| } |
| |
| Status WriteHandler::OnWriteRequest(Messaging::ExchangeContext * apExchangeContext, System::PacketBufferHandle && aPayload, |
| bool aIsTimedWrite) |
| { |
| // |
| // Let's take over further message processing on this exchange from the IM. |
| // This is only relevant during chunked requests. |
| // |
| mExchangeCtx.Grab(apExchangeContext); |
| |
| Status status = HandleWriteRequestMessage(apExchangeContext, std::move(aPayload), aIsTimedWrite); |
| |
| // The write transaction will be alive only when the message was handled successfully and there are more chunks. |
| if (!(status == Status::Success && mStateFlags.Has(StateBits::kHasMoreChunks))) |
| { |
| Close(); |
| } |
| |
| return status; |
| } |
| |
| CHIP_ERROR WriteHandler::OnMessageReceived(Messaging::ExchangeContext * apExchangeContext, const PayloadHeader & aPayloadHeader, |
| System::PacketBufferHandle && aPayload) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| |
| VerifyOrDieWithMsg(apExchangeContext == mExchangeCtx.Get(), DataManagement, |
| "Incoming exchange context should be same as the initial request."); |
| VerifyOrDieWithMsg(!apExchangeContext->IsGroupExchangeContext(), DataManagement, |
| "OnMessageReceived should not be called on GroupExchangeContext"); |
| if (!aPayloadHeader.HasMessageType(Protocols::InteractionModel::MsgType::WriteRequest)) |
| { |
| if (aPayloadHeader.HasMessageType(Protocols::InteractionModel::MsgType::StatusResponse)) |
| { |
| CHIP_ERROR statusError = CHIP_NO_ERROR; |
| // Parse the status response so we can log it properly. |
| StatusResponse::ProcessStatusResponse(std::move(aPayload), statusError); |
| } |
| ChipLogDetail(DataManagement, "Unexpected message type %d", aPayloadHeader.GetMessageType()); |
| StatusResponse::Send(Status::InvalidAction, apExchangeContext, false /*aExpectResponse*/); |
| Close(); |
| return CHIP_ERROR_INVALID_MESSAGE_TYPE; |
| } |
| |
| Status status = |
| HandleWriteRequestMessage(apExchangeContext, std::move(aPayload), false /* chunked write should not be timed write */); |
| if (status == Status::Success) |
| { |
| // We have no more chunks, the write response has been sent in HandleWriteRequestMessage, so close directly. |
| if (!mStateFlags.Has(StateBits::kHasMoreChunks)) |
| { |
| Close(); |
| } |
| } |
| else |
| { |
| err = StatusResponse::Send(status, apExchangeContext, false /*aExpectResponse*/); |
| Close(); |
| } |
| return CHIP_NO_ERROR; |
| } |
| |
| void WriteHandler::OnResponseTimeout(Messaging::ExchangeContext * apExchangeContext) |
| { |
| ChipLogError(DataManagement, "Time out! failed to receive status response from Exchange: " ChipLogFormatExchange, |
| ChipLogValueExchange(apExchangeContext)); |
| Close(); |
| } |
| |
| CHIP_ERROR WriteHandler::FinalizeMessage(System::PacketBufferTLVWriter && aMessageWriter, System::PacketBufferHandle & packet) |
| { |
| VerifyOrReturnError(mState == State::AddStatus, CHIP_ERROR_INCORRECT_STATE); |
| ReturnErrorOnFailure(mWriteResponseBuilder.GetWriteResponses().EndOfAttributeStatuses()); |
| ReturnErrorOnFailure(mWriteResponseBuilder.EndOfWriteResponseMessage()); |
| ReturnErrorOnFailure(aMessageWriter.Finalize(&packet)); |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR WriteHandler::SendWriteResponse(System::PacketBufferTLVWriter && aMessageWriter) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| System::PacketBufferHandle packet; |
| |
| VerifyOrExit(mState == State::AddStatus, err = CHIP_ERROR_INCORRECT_STATE); |
| |
| err = FinalizeMessage(std::move(aMessageWriter), packet); |
| SuccessOrExit(err); |
| |
| VerifyOrExit(mExchangeCtx, err = CHIP_ERROR_INCORRECT_STATE); |
| mExchangeCtx->UseSuggestedResponseTimeout(app::kExpectedIMProcessingTime); |
| err = mExchangeCtx->SendMessage(Protocols::InteractionModel::MsgType::WriteResponse, std::move(packet), |
| mStateFlags.Has(StateBits::kHasMoreChunks) ? Messaging::SendMessageFlags::kExpectResponse |
| : Messaging::SendMessageFlags::kNone); |
| SuccessOrExit(err); |
| |
| MoveToState(State::Sending); |
| |
| exit: |
| return err; |
| } |
| |
| void WriteHandler::DeliverListWriteBegin(const ConcreteAttributePath & aPath) |
| { |
| if (auto * attrOverride = AttributeAccessInterfaceRegistry::Instance().Get(aPath.mEndpointId, aPath.mClusterId)) |
| { |
| attrOverride->OnListWriteBegin(aPath); |
| } |
| } |
| |
| void WriteHandler::DeliverListWriteEnd(const ConcreteAttributePath & aPath, bool writeWasSuccessful) |
| { |
| if (auto * attrOverride = AttributeAccessInterfaceRegistry::Instance().Get(aPath.mEndpointId, aPath.mClusterId)) |
| { |
| attrOverride->OnListWriteEnd(aPath, writeWasSuccessful); |
| } |
| } |
| |
| void WriteHandler::DeliverFinalListWriteEnd(bool writeWasSuccessful) |
| { |
| if (mProcessingAttributePath.HasValue() && mStateFlags.Has(StateBits::kProcessingAttributeIsList)) |
| { |
| DeliverListWriteEnd(mProcessingAttributePath.Value(), writeWasSuccessful); |
| } |
| mProcessingAttributePath.ClearValue(); |
| } |
| |
| CHIP_ERROR WriteHandler::DeliverFinalListWriteEndForGroupWrite(bool writeWasSuccessful) |
| { |
| VerifyOrReturnError(mProcessingAttributePath.HasValue() && mStateFlags.Has(StateBits::kProcessingAttributeIsList), |
| CHIP_NO_ERROR); |
| |
| Credentials::GroupDataProvider::GroupEndpoint mapping; |
| Credentials::GroupDataProvider * groupDataProvider = Credentials::GetGroupDataProvider(); |
| Credentials::GroupDataProvider::EndpointIterator * iterator; |
| |
| GroupId groupId = mExchangeCtx->GetSessionHandle()->AsIncomingGroupSession()->GetGroupId(); |
| FabricIndex fabricIndex = GetAccessingFabricIndex(); |
| |
| auto processingConcreteAttributePath = mProcessingAttributePath.Value(); |
| mProcessingAttributePath.ClearValue(); |
| |
| iterator = groupDataProvider->IterateEndpoints(fabricIndex); |
| VerifyOrReturnError(iterator != nullptr, CHIP_ERROR_NO_MEMORY); |
| |
| while (iterator->Next(mapping)) |
| { |
| if (groupId != mapping.group_id) |
| { |
| continue; |
| } |
| |
| processingConcreteAttributePath.mEndpointId = mapping.endpoint_id; |
| |
| VerifyOrReturnError(mDelegate, CHIP_ERROR_INCORRECT_STATE); |
| if (!mDelegate->HasConflictWriteRequests(this, processingConcreteAttributePath)) |
| { |
| DeliverListWriteEnd(processingConcreteAttributePath, writeWasSuccessful); |
| } |
| } |
| iterator->Release(); |
| return CHIP_NO_ERROR; |
| } |
| namespace { |
| |
| // To reduce the various use of previousProcessed.HasValue() && previousProcessed.Value() == nextAttribute to save code size. |
| bool IsSameAttribute(const Optional<ConcreteAttributePath> & previousProcessed, const ConcreteDataAttributePath & nextAttribute) |
| { |
| return previousProcessed.HasValue() && previousProcessed.Value() == nextAttribute; |
| } |
| |
| bool ShouldReportListWriteEnd(const Optional<ConcreteAttributePath> & previousProcessed, bool previousProcessedAttributeIsList, |
| const ConcreteDataAttributePath & nextAttribute) |
| { |
| return previousProcessedAttributeIsList && !IsSameAttribute(previousProcessed, nextAttribute) && previousProcessed.HasValue(); |
| } |
| |
| bool ShouldReportListWriteBegin(const Optional<ConcreteAttributePath> & previousProcessed, bool previousProcessedAttributeIsList, |
| const ConcreteDataAttributePath & nextAttribute) |
| { |
| return !IsSameAttribute(previousProcessed, nextAttribute) && nextAttribute.IsListOperation(); |
| } |
| |
| } // namespace |
| |
| CHIP_ERROR WriteHandler::ProcessAttributeDataIBs(TLV::TLVReader & aAttributeDataIBsReader) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| |
| VerifyOrReturnError(mExchangeCtx, CHIP_ERROR_INTERNAL); |
| const Access::SubjectDescriptor subjectDescriptor = mExchangeCtx->GetSessionHandle()->GetSubjectDescriptor(); |
| |
| while (CHIP_NO_ERROR == (err = aAttributeDataIBsReader.Next())) |
| { |
| chip::TLV::TLVReader dataReader; |
| AttributeDataIB::Parser element; |
| AttributePathIB::Parser attributePath; |
| ConcreteDataAttributePath dataAttributePath; |
| TLV::TLVReader reader = aAttributeDataIBsReader; |
| |
| err = element.Init(reader); |
| SuccessOrExit(err); |
| |
| err = element.GetPath(&attributePath); |
| SuccessOrExit(err); |
| |
| err = attributePath.GetConcreteAttributePath(dataAttributePath); |
| SuccessOrExit(err); |
| |
| err = element.GetData(&dataReader); |
| SuccessOrExit(err); |
| |
| if (!dataAttributePath.IsListOperation() && IsListAttributePath(dataAttributePath).value_or(false)) |
| { |
| dataAttributePath.mListOp = ConcreteDataAttributePath::ListOperation::ReplaceAll; |
| } |
| |
| VerifyOrExit(mDelegate, err = CHIP_ERROR_INCORRECT_STATE); |
| if (mDelegate->HasConflictWriteRequests(this, dataAttributePath) || |
| // Per chunking protocol, we are processing the list entries, but the initial empty list is not processed, so we reject |
| // it with Busy status code. |
| (dataAttributePath.IsListItemOperation() && !IsSameAttribute(mProcessingAttributePath, dataAttributePath))) |
| { |
| err = AddStatusInternal(dataAttributePath, StatusIB(Status::Busy)); |
| continue; |
| } |
| |
| if (ShouldReportListWriteEnd(mProcessingAttributePath, mStateFlags.Has(StateBits::kProcessingAttributeIsList), |
| dataAttributePath)) |
| { |
| DeliverListWriteEnd(mProcessingAttributePath.Value(), mStateFlags.Has(StateBits::kAttributeWriteSuccessful)); |
| } |
| |
| if (ShouldReportListWriteBegin(mProcessingAttributePath, mStateFlags.Has(StateBits::kProcessingAttributeIsList), |
| dataAttributePath)) |
| { |
| DeliverListWriteBegin(dataAttributePath); |
| mStateFlags.Set(StateBits::kAttributeWriteSuccessful); |
| } |
| |
| mStateFlags.Set(StateBits::kProcessingAttributeIsList, dataAttributePath.IsListOperation()); |
| mProcessingAttributePath.SetValue(dataAttributePath); |
| |
| DataModelCallbacks::GetInstance()->AttributeOperation(DataModelCallbacks::OperationType::Write, |
| DataModelCallbacks::OperationOrder::Pre, dataAttributePath); |
| |
| TLV::TLVWriter backup; |
| DataVersion version = 0; |
| mWriteResponseBuilder.GetWriteResponses().Checkpoint(backup); |
| err = element.GetDataVersion(&version); |
| if (CHIP_NO_ERROR == err) |
| { |
| dataAttributePath.mDataVersion.SetValue(version); |
| } |
| else if (CHIP_END_OF_TLV == err) |
| { |
| err = CHIP_NO_ERROR; |
| } |
| SuccessOrExit(err); |
| err = WriteClusterData(subjectDescriptor, dataAttributePath, dataReader); |
| if (err != CHIP_NO_ERROR) |
| { |
| mWriteResponseBuilder.GetWriteResponses().Rollback(backup); |
| err = AddStatusInternal(dataAttributePath, StatusIB(err)); |
| } |
| |
| DataModelCallbacks::GetInstance()->AttributeOperation(DataModelCallbacks::OperationType::Write, |
| DataModelCallbacks::OperationOrder::Post, dataAttributePath); |
| SuccessOrExit(err); |
| } |
| |
| if (CHIP_END_OF_TLV == err) |
| { |
| err = CHIP_NO_ERROR; |
| } |
| |
| SuccessOrExit(err); |
| |
| if (!mStateFlags.Has(StateBits::kHasMoreChunks)) |
| { |
| DeliverFinalListWriteEnd(mStateFlags.Has(StateBits::kAttributeWriteSuccessful)); |
| } |
| |
| exit: |
| return err; |
| } |
| |
| CHIP_ERROR WriteHandler::ProcessGroupAttributeDataIBs(TLV::TLVReader & aAttributeDataIBsReader) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| |
| VerifyOrReturnError(mExchangeCtx, CHIP_ERROR_INTERNAL); |
| const Access::SubjectDescriptor subjectDescriptor = |
| mExchangeCtx->GetSessionHandle()->AsIncomingGroupSession()->GetSubjectDescriptor(); |
| |
| GroupId groupId = mExchangeCtx->GetSessionHandle()->AsIncomingGroupSession()->GetGroupId(); |
| FabricIndex fabric = GetAccessingFabricIndex(); |
| |
| while (CHIP_NO_ERROR == (err = aAttributeDataIBsReader.Next())) |
| { |
| chip::TLV::TLVReader dataReader; |
| AttributeDataIB::Parser element; |
| AttributePathIB::Parser attributePath; |
| ConcreteDataAttributePath dataAttributePath; |
| TLV::TLVReader reader = aAttributeDataIBsReader; |
| |
| err = element.Init(reader); |
| SuccessOrExit(err); |
| |
| err = element.GetPath(&attributePath); |
| SuccessOrExit(err); |
| |
| err = attributePath.GetGroupAttributePath(dataAttributePath); |
| SuccessOrExit(err); |
| |
| err = element.GetData(&dataReader); |
| SuccessOrExit(err); |
| |
| if (!dataAttributePath.IsListOperation() && dataReader.GetType() == TLV::TLVType::kTLVType_Array) |
| { |
| dataAttributePath.mListOp = ConcreteDataAttributePath::ListOperation::ReplaceAll; |
| } |
| |
| ChipLogDetail(DataManagement, |
| "Received group attribute write for Group=%u Cluster=" ChipLogFormatMEI " attribute=" ChipLogFormatMEI, |
| groupId, ChipLogValueMEI(dataAttributePath.mClusterId), ChipLogValueMEI(dataAttributePath.mAttributeId)); |
| |
| AutoReleaseGroupEndpointIterator iterator(Credentials::GetGroupDataProvider()->IterateEndpoints(fabric)); |
| VerifyOrExit(!iterator.IsNull(), err = CHIP_ERROR_NO_MEMORY); |
| |
| bool shouldReportListWriteEnd = ShouldReportListWriteEnd( |
| mProcessingAttributePath, mStateFlags.Has(StateBits::kProcessingAttributeIsList), dataAttributePath); |
| bool shouldReportListWriteBegin = false; // This will be set below. |
| |
| std::optional<bool> isListAttribute = std::nullopt; |
| |
| Credentials::GroupDataProvider::GroupEndpoint mapping; |
| while (iterator.Next(mapping)) |
| { |
| if (groupId != mapping.group_id) |
| { |
| continue; |
| } |
| |
| dataAttributePath.mEndpointId = mapping.endpoint_id; |
| |
| // Try to get the metadata from for the attribute from one of the expanded endpoints (it doesn't really matter which |
| // endpoint we pick, as long as it's valid) and update the path info according to it and recheck if we need to report |
| // list write begin. |
| if (!isListAttribute.has_value()) |
| { |
| isListAttribute = IsListAttributePath(dataAttributePath); |
| bool currentAttributeIsList = isListAttribute.value_or(false); |
| |
| if (!dataAttributePath.IsListOperation() && currentAttributeIsList) |
| { |
| dataAttributePath.mListOp = ConcreteDataAttributePath::ListOperation::ReplaceAll; |
| } |
| ConcreteDataAttributePath pathForCheckingListWriteBegin(kInvalidEndpointId, dataAttributePath.mClusterId, |
| dataAttributePath.mEndpointId, dataAttributePath.mListOp, |
| dataAttributePath.mListIndex); |
| shouldReportListWriteBegin = |
| ShouldReportListWriteBegin(mProcessingAttributePath, mStateFlags.Has(StateBits::kProcessingAttributeIsList), |
| pathForCheckingListWriteBegin); |
| } |
| |
| if (shouldReportListWriteEnd) |
| { |
| auto processingConcreteAttributePath = mProcessingAttributePath.Value(); |
| processingConcreteAttributePath.mEndpointId = mapping.endpoint_id; |
| VerifyOrExit(mDelegate, err = CHIP_ERROR_INCORRECT_STATE); |
| if (mDelegate->HasConflictWriteRequests(this, processingConcreteAttributePath)) |
| { |
| DeliverListWriteEnd(processingConcreteAttributePath, true /* writeWasSuccessful */); |
| } |
| } |
| |
| VerifyOrExit(mDelegate, err = CHIP_ERROR_INCORRECT_STATE); |
| if (mDelegate->HasConflictWriteRequests(this, dataAttributePath)) |
| { |
| ChipLogDetail(DataManagement, |
| "Writing attribute endpoint=%u Cluster=" ChipLogFormatMEI " attribute=" ChipLogFormatMEI |
| " is conflict with other write transactions.", |
| mapping.endpoint_id, ChipLogValueMEI(dataAttributePath.mClusterId), |
| ChipLogValueMEI(dataAttributePath.mAttributeId)); |
| continue; |
| } |
| |
| if (shouldReportListWriteBegin) |
| { |
| DeliverListWriteBegin(dataAttributePath); |
| } |
| |
| ChipLogDetail(DataManagement, |
| "Processing group attribute write for endpoint=%u Cluster=" ChipLogFormatMEI |
| " attribute=" ChipLogFormatMEI, |
| mapping.endpoint_id, ChipLogValueMEI(dataAttributePath.mClusterId), |
| ChipLogValueMEI(dataAttributePath.mAttributeId)); |
| |
| chip::TLV::TLVReader tmpDataReader(dataReader); |
| |
| DataModelCallbacks::GetInstance()->AttributeOperation(DataModelCallbacks::OperationType::Write, |
| DataModelCallbacks::OperationOrder::Pre, dataAttributePath); |
| err = WriteClusterData(subjectDescriptor, dataAttributePath, tmpDataReader); |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogError(DataManagement, |
| "WriteClusterData Endpoint=%u Cluster=" ChipLogFormatMEI " Attribute =" ChipLogFormatMEI |
| " failed: %" CHIP_ERROR_FORMAT, |
| mapping.endpoint_id, ChipLogValueMEI(dataAttributePath.mClusterId), |
| ChipLogValueMEI(dataAttributePath.mAttributeId), err.Format()); |
| } |
| DataModelCallbacks::GetInstance()->AttributeOperation(DataModelCallbacks::OperationType::Write, |
| DataModelCallbacks::OperationOrder::Post, dataAttributePath); |
| } |
| |
| dataAttributePath.mEndpointId = kInvalidEndpointId; |
| mStateFlags.Set(StateBits::kProcessingAttributeIsList, dataAttributePath.IsListOperation()); |
| mProcessingAttributePath.SetValue(dataAttributePath); |
| } |
| |
| if (CHIP_END_OF_TLV == err) |
| { |
| err = CHIP_NO_ERROR; |
| } |
| |
| err = DeliverFinalListWriteEndForGroupWrite(true); |
| |
| exit: |
| // The DeliverFinalListWriteEndForGroupWrite above will deliver the successful state of the list write and clear the |
| // mProcessingAttributePath making the following call no-op. So we call it again after the exit label to deliver a failure state |
| // to the clusters. Ignore the error code since we need to deliver other more important failures. |
| DeliverFinalListWriteEndForGroupWrite(false); |
| return err; |
| } |
| |
| Status WriteHandler::ProcessWriteRequest(System::PacketBufferHandle && aPayload, bool aIsTimedWrite) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| System::PacketBufferTLVReader reader; |
| |
| WriteRequestMessage::Parser writeRequestParser; |
| AttributeDataIBs::Parser AttributeDataIBsParser; |
| TLV::TLVReader AttributeDataIBsReader; |
| // Default to InvalidAction for our status; that's what we want if any of |
| // the parsing of our overall structure or paths fails. Once we have a |
| // successfully parsed path, the only way we will get a failure return is if |
| // our path handling fails to AddStatus on us. |
| // |
| // TODO: That's not technically InvalidAction, and we should probably make |
| // our callees hand out Status as well. |
| Status status = Status::InvalidAction; |
| |
| #if CHIP_CONFIG_USE_DATA_MODEL_INTERFACE |
| mLastSuccessfullyWrittenPath = std::nullopt; |
| #endif // CHIP_CONFIG_USE_DATA_MODEL_INTERFACE |
| |
| reader.Init(std::move(aPayload)); |
| |
| err = writeRequestParser.Init(reader); |
| SuccessOrExit(err); |
| |
| #if CHIP_CONFIG_IM_PRETTY_PRINT |
| writeRequestParser.PrettyPrint(); |
| #endif // CHIP_CONFIG_IM_PRETTY_PRINT |
| bool boolValue; |
| |
| boolValue = mStateFlags.Has(StateBits::kSuppressResponse); |
| err = writeRequestParser.GetSuppressResponse(&boolValue); |
| if (err == CHIP_END_OF_TLV) |
| { |
| err = CHIP_NO_ERROR; |
| } |
| SuccessOrExit(err); |
| mStateFlags.Set(StateBits::kSuppressResponse, boolValue); |
| |
| boolValue = mStateFlags.Has(StateBits::kIsTimedRequest); |
| err = writeRequestParser.GetTimedRequest(&boolValue); |
| SuccessOrExit(err); |
| mStateFlags.Set(StateBits::kIsTimedRequest, boolValue); |
| |
| boolValue = mStateFlags.Has(StateBits::kHasMoreChunks); |
| err = writeRequestParser.GetMoreChunkedMessages(&boolValue); |
| if (err == CHIP_ERROR_END_OF_TLV) |
| { |
| err = CHIP_NO_ERROR; |
| } |
| SuccessOrExit(err); |
| mStateFlags.Set(StateBits::kHasMoreChunks, boolValue); |
| |
| if (mStateFlags.Has(StateBits::kHasMoreChunks) && |
| (mExchangeCtx->IsGroupExchangeContext() || mStateFlags.Has(StateBits::kIsTimedRequest))) |
| { |
| // Sanity check: group exchange context should only have one chunk. |
| // Also, timed requests should not have more than one chunk. |
| ExitNow(err = CHIP_ERROR_INVALID_MESSAGE_TYPE); |
| } |
| |
| err = writeRequestParser.GetWriteRequests(&AttributeDataIBsParser); |
| SuccessOrExit(err); |
| |
| if (mStateFlags.Has(StateBits::kIsTimedRequest) != aIsTimedWrite) |
| { |
| // The message thinks it should be part of a timed interaction but it's |
| // not, or vice versa. |
| status = Status::TimedRequestMismatch; |
| goto exit; |
| } |
| |
| AttributeDataIBsParser.GetReader(&AttributeDataIBsReader); |
| |
| if (mExchangeCtx->IsGroupExchangeContext()) |
| { |
| err = ProcessGroupAttributeDataIBs(AttributeDataIBsReader); |
| } |
| else |
| { |
| err = ProcessAttributeDataIBs(AttributeDataIBsReader); |
| } |
| SuccessOrExit(err); |
| SuccessOrExit(err = writeRequestParser.ExitContainer()); |
| |
| if (err == CHIP_NO_ERROR) |
| { |
| status = Status::Success; |
| } |
| |
| exit: |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogError(DataManagement, "Failed to process write request: %" CHIP_ERROR_FORMAT, err.Format()); |
| } |
| return status; |
| } |
| |
| CHIP_ERROR WriteHandler::AddStatus(const ConcreteDataAttributePath & aPath, |
| const Protocols::InteractionModel::ClusterStatusCode & aStatus) |
| { |
| return AddStatusInternal(aPath, StatusIB{ aStatus }); |
| } |
| |
| CHIP_ERROR WriteHandler::AddClusterSpecificSuccess(const ConcreteDataAttributePath & aPath, ClusterStatus aClusterStatus) |
| { |
| return AddStatus(aPath, Protocols::InteractionModel::ClusterStatusCode::ClusterSpecificSuccess(aClusterStatus)); |
| } |
| |
| CHIP_ERROR WriteHandler::AddClusterSpecificFailure(const ConcreteDataAttributePath & aPath, ClusterStatus aClusterStatus) |
| { |
| return AddStatus(aPath, Protocols::InteractionModel::ClusterStatusCode::ClusterSpecificFailure(aClusterStatus)); |
| } |
| |
| CHIP_ERROR WriteHandler::AddStatusInternal(const ConcreteDataAttributePath & aPath, const StatusIB & aStatus) |
| { |
| AttributeStatusIBs::Builder & writeResponses = mWriteResponseBuilder.GetWriteResponses(); |
| AttributeStatusIB::Builder & attributeStatusIB = writeResponses.CreateAttributeStatus(); |
| |
| if (!aStatus.IsSuccess()) |
| { |
| mStateFlags.Clear(StateBits::kAttributeWriteSuccessful); |
| } |
| |
| ReturnErrorOnFailure(writeResponses.GetError()); |
| |
| AttributePathIB::Builder & path = attributeStatusIB.CreatePath(); |
| ReturnErrorOnFailure(attributeStatusIB.GetError()); |
| ReturnErrorOnFailure(path.Encode(aPath)); |
| |
| StatusIB::Builder & statusIBBuilder = attributeStatusIB.CreateErrorStatus(); |
| ReturnErrorOnFailure(attributeStatusIB.GetError()); |
| statusIBBuilder.EncodeStatusIB(aStatus); |
| ReturnErrorOnFailure(statusIBBuilder.GetError()); |
| ReturnErrorOnFailure(attributeStatusIB.EndOfAttributeStatusIB()); |
| |
| MoveToState(State::AddStatus); |
| return CHIP_NO_ERROR; |
| } |
| |
| FabricIndex WriteHandler::GetAccessingFabricIndex() const |
| { |
| return mExchangeCtx->GetSessionHandle()->GetFabricIndex(); |
| } |
| |
| const char * WriteHandler::GetStateStr() const |
| { |
| #if CHIP_DETAIL_LOGGING |
| switch (mState) |
| { |
| case State::Uninitialized: |
| return "Uninitialized"; |
| |
| case State::Initialized: |
| return "Initialized"; |
| |
| case State::AddStatus: |
| return "AddStatus"; |
| case State::Sending: |
| return "Sending"; |
| } |
| #endif // CHIP_DETAIL_LOGGING |
| return "N/A"; |
| } |
| |
| void WriteHandler::MoveToState(const State aTargetState) |
| { |
| mState = aTargetState; |
| ChipLogDetail(DataManagement, "IM WH moving to [%s]", GetStateStr()); |
| } |
| |
| CHIP_ERROR WriteHandler::WriteClusterData(const Access::SubjectDescriptor & aSubject, const ConcreteDataAttributePath & aPath, |
| TLV::TLVReader & aData) |
| { |
| // Writes do not have a checked-path. If data model interface is enabled (both checked and only version) |
| // the write is done via the DataModel interface |
| #if CHIP_CONFIG_USE_DATA_MODEL_INTERFACE |
| VerifyOrReturnError(mDataModelProvider != nullptr, CHIP_ERROR_INCORRECT_STATE); |
| |
| DataModel::WriteAttributeRequest request; |
| |
| request.path = aPath; |
| request.subjectDescriptor = aSubject; |
| request.previousSuccessPath = mLastSuccessfullyWrittenPath; |
| request.writeFlags.Set(DataModel::WriteFlags::kTimed, IsTimedWrite()); |
| |
| AttributeValueDecoder decoder(aData, aSubject); |
| |
| DataModel::ActionReturnStatus status = mDataModelProvider->WriteAttribute(request, decoder); |
| |
| mLastSuccessfullyWrittenPath = status.IsSuccess() ? std::make_optional(aPath) : std::nullopt; |
| |
| return AddStatusInternal(aPath, StatusIB(status.GetStatusCode())); |
| #else |
| return WriteSingleClusterData(aSubject, aPath, aData, this); |
| #endif // CHIP_CONFIG_USE_DATA_MODEL_INTERFACE |
| } |
| |
| } // namespace app |
| } // namespace chip |