/* * * 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. */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace chip { namespace app { class WriteHandler; class WriteHandlerDelegate { public: virtual ~WriteHandlerDelegate() = default; /** * Returns whether the write operation to the given path is in conflict with another write operation. * (i.e. another write transaction is in the middle of processing a chunked write to the given path.) */ virtual bool HasConflictWriteRequests(const WriteHandler * apWriteHandler, const ConcreteAttributePath & aPath) = 0; }; /** * @brief The write handler is responsible for processing a write request and sending a write reply. */ class WriteHandler : public Messaging::ExchangeDelegate { public: WriteHandler() : mExchangeCtx(*this) {} /** * Initialize the WriteHandler. Within the lifetime * of this instance, this method is invoked once after object * construction until a call to Close is made to terminate the * instance. * * @param[in] apProvider A valid pointer to the model used to forward writes towards * @param[in] apWriteHandlerDelegate A Valid pointer to the WriteHandlerDelegate. * * @retval #CHIP_ERROR_INVALID_ARGUMENT on invalid pointers * @retval #CHIP_ERROR_INCORRECT_STATE If the state is not equal to * kState_NotInitialized. * @retval #CHIP_NO_ERROR On success. * */ CHIP_ERROR Init(DataModel::Provider * apProvider, WriteHandlerDelegate * apWriteHandlerDelegate); /** * Process a write request. Parts of the processing may end up being asynchronous, but the WriteHandler * guarantees that it will call Close on itself when processing is done (including if OnWriteRequest * returns an error). * * @param[in] apExchangeContext A pointer to the ExchangeContext. * @param[in] aPayload A payload that has read request data * @param[in] aIsTimedWrite Whether write is part of a timed interaction. * * @retval Status. Callers are expected to send a status response if the * return status is not Status::Success. */ Protocols::InteractionModel::Status OnWriteRequest(Messaging::ExchangeContext * apExchangeContext, System::PacketBufferHandle && aPayload, bool aIsTimedWrite); /** * Clean up state when we are done sending the write response. */ void Close(); bool IsFree() const { return mState == State::Uninitialized; } ~WriteHandler() override = default; CHIP_ERROR ProcessAttributeDataIBs(TLV::TLVReader & aAttributeDataIBsReader); CHIP_ERROR ProcessGroupAttributeDataIBs(TLV::TLVReader & aAttributeDataIBsReader); CHIP_ERROR AddStatus(const ConcreteDataAttributePath & aPath, const Protocols::InteractionModel::ClusterStatusCode & aStatus); CHIP_ERROR AddStatus(const ConcreteDataAttributePath & aPath, const Protocols::InteractionModel::Status aStatus) { return AddStatus(aPath, Protocols::InteractionModel::ClusterStatusCode{ aStatus }); } CHIP_ERROR AddClusterSpecificSuccess(const ConcreteDataAttributePath & aAttributePathParams, ClusterStatus aClusterStatus); CHIP_ERROR AddClusterSpecificFailure(const ConcreteDataAttributePath & aAttributePathParams, ClusterStatus aClusterStatus); FabricIndex GetAccessingFabricIndex() const; /** * Check whether the WriteRequest we are handling is a timed write. */ bool IsTimedWrite() const { return mStateFlags.Has(StateBits::kIsTimedRequest); } bool MatchesExchangeContext(Messaging::ExchangeContext * apExchangeContext) const { return !IsFree() && mExchangeCtx.Get() == apExchangeContext; } void CacheACLCheckResult(const AttributeAccessToken & aToken) { mACLCheckCache.SetValue(aToken); } bool ACLCheckCacheHit(const AttributeAccessToken & aToken) { return mACLCheckCache.HasValue() && mACLCheckCache.Value() == aToken; } bool IsCurrentlyProcessingWritePath(const ConcreteAttributePath & aPath) { return mProcessingAttributePath.HasValue() && mProcessingAttributePath.Value() == aPath; } private: friend class TestWriteInteraction; enum class State : uint8_t { Uninitialized = 0, // The handler has not been initialized Initialized, // The handler has been initialized and is ready AddStatus, // The handler has added status code Sending, // The handler has sent out the write response }; using Status = Protocols::InteractionModel::Status; Status ProcessWriteRequest(System::PacketBufferHandle && aPayload, bool aIsTimedWrite); Status HandleWriteRequestMessage(Messaging::ExchangeContext * apExchangeContext, System::PacketBufferHandle && aPayload, bool aIsTimedWrite); CHIP_ERROR FinalizeMessage(System::PacketBufferTLVWriter && aMessageWriter, System::PacketBufferHandle & packet); CHIP_ERROR SendWriteResponse(System::PacketBufferTLVWriter && aMessageWriter); void MoveToState(const State aTargetState); const char * GetStateStr() const; void DeliverListWriteBegin(const ConcreteAttributePath & aPath); void DeliverListWriteEnd(const ConcreteAttributePath & aPath, bool writeWasSuccessful); // Deliver the signal that we have delivered all list entries to the AttributeAccessInterface. This function will be called // after handling the last chunk of a series of write requests. Or the write handler was shutdown (usually due to transport // timeout). // This function will become no-op on group writes, since DeliverFinalListWriteEndForGroupWrite will clear the // mProcessingAttributePath after processing the AttributeDataIBs from the request. void DeliverFinalListWriteEnd(bool writeWasSuccessful); // Deliver the signal that we have delivered all list entries to the AttributeAccessInterface. This function will be called // after handling the last attribute in a group write request (since group writes will never be chunked writes). Or we failed to // process the group write request (usually due to malformed messages). This function should only be called by // ProcessGroupAttributeDataIBs. CHIP_ERROR DeliverFinalListWriteEndForGroupWrite(bool writeWasSuccessful); CHIP_ERROR AddStatusInternal(const ConcreteDataAttributePath & aPath, const StatusIB & aStatus); // ExchangeDelegate CHIP_ERROR OnMessageReceived(Messaging::ExchangeContext * apExchangeContext, const PayloadHeader & aPayloadHeader, System::PacketBufferHandle && aPayload) override; void OnResponseTimeout(Messaging::ExchangeContext * apExchangeContext) override; // Write the given data to the given path CHIP_ERROR WriteClusterData(const Access::SubjectDescriptor & aSubject, const ConcreteDataAttributePath & aPath, TLV::TLVReader & aData); /// Checks whether the given path corresponds to a list attribute /// Return values: /// true/false: valid attribute path, known if list or not /// std::nulloptr - path not available/valid, unknown if attribute is a list or not std::optional IsListAttributePath(const ConcreteAttributePath & path); Messaging::ExchangeHolder mExchangeCtx; WriteResponseMessage::Builder mWriteResponseBuilder; Optional mProcessingAttributePath; Optional mACLCheckCache = NullOptional; #if CHIP_CONFIG_USE_DATA_MODEL_INTERFACE DataModel::Provider * mDataModelProvider = nullptr; std::optional mLastSuccessfullyWrittenPath; #endif // This may be a "fake" pointer or a real delegate pointer, depending // on CHIP_CONFIG_STATIC_GLOBAL_INTERACTION_MODEL_ENGINE setting. // // When this is not a real pointer, it checks that the value is always // set to the global InteractionModelEngine and the size of this // member is 1 byte. InteractionModelDelegatePointer mDelegate; // bit level enums to save storage for this object. InteractionModelEngine maintains // several of these objects, so every bit of storage multiplies storage usage. enum class StateBits : uint8_t { kIsTimedRequest = 0x01, kSuppressResponse = 0x02, kHasMoreChunks = 0x04, kProcessingAttributeIsList = 0x08, // We record the Status when AddStatus is called to determine whether all data of a list write is accepted. // This value will be used by DeliverListWriteEnd and DeliverFinalListWriteEnd but it won't be used by group writes based on // the fact that the errors that won't be delivered to AttributeAccessInterface are: // (1) Attribute not found // (2) Access control failed // (3) Write request to a read-only attribute // (4) Data version mismatch // (5) Not using timed write. // Where (1)-(3) will be consistent among the whole list write request, while (4) and (5) are not appliable to group // writes. kAttributeWriteSuccessful = 0x10, }; BitFlags mStateFlags; State mState = State::Uninitialized; }; } // namespace app } // namespace chip