| /* |
| * |
| * 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 <app/BufferedReadCallback.h> |
| #include <app/ConcreteAttributePath.h> |
| #include <app/data-model/Decode.h> |
| #include <functional> |
| #include <lib/support/CHIPMem.h> |
| |
| namespace chip { |
| namespace Controller { |
| |
| /* |
| * This provides an adapter class that implements ReadClient::Callback and provides three additional |
| * features: |
| * 1. The ability to pass in std::function closures to permit more flexible |
| * programming scenarios than are provided by the strict delegate interface |
| * stipulated by ReadClient::Callback. |
| * |
| * 2. Automatic decoding of attribute data provided in the TLVReader by |
| * ReadClient::Callback::OnAttributeData into a decoded cluster object. |
| * |
| * 3. Automatically representing all errors as a CHIP_ERROR (which might |
| * encapsulate a StatusIB). This could be a path-specific error or it |
| * could be a general error for the entire request; the distinction is not |
| * that important, because we only have one path involved. If the |
| * CHIP_ERROR encapsulates a StatusIB, StatusIB::InitFromChipError can be |
| * used to extract the status. |
| */ |
| template <typename DecodableAttributeType> |
| class TypedReadAttributeCallback final : public app::ReadClient::Callback |
| { |
| public: |
| using OnSuccessCallbackType = |
| std::function<void(const app::ConcreteDataAttributePath & aPath, const DecodableAttributeType & aData)>; |
| using OnErrorCallbackType = std::function<void(const app::ConcreteDataAttributePath * aPath, CHIP_ERROR aError)>; |
| using OnDoneCallbackType = std::function<void(TypedReadAttributeCallback * callback)>; |
| using OnSubscriptionEstablishedCallbackType = std::function<void(const app::ReadClient & readClient)>; |
| using OnResubscriptionAttemptCallbackType = |
| std::function<void(const app::ReadClient & readClient, CHIP_ERROR aError, uint32_t aNextResubscribeIntervalMsec)>; |
| TypedReadAttributeCallback(ClusterId aClusterId, AttributeId aAttributeId, OnSuccessCallbackType aOnSuccess, |
| OnErrorCallbackType aOnError, OnDoneCallbackType aOnDone, |
| OnSubscriptionEstablishedCallbackType aOnSubscriptionEstablished = nullptr, |
| OnResubscriptionAttemptCallbackType aOnResubscriptionAttempt = nullptr) : |
| mClusterId(aClusterId), |
| mAttributeId(aAttributeId), mOnSuccess(aOnSuccess), mOnError(aOnError), mOnDone(aOnDone), |
| mOnSubscriptionEstablished(aOnSubscriptionEstablished), mOnResubscriptionAttempt(aOnResubscriptionAttempt), |
| mBufferedReadAdapter(*this) |
| {} |
| |
| ~TypedReadAttributeCallback() |
| { |
| // Ensure we release the ReadClient before we tear down anything else, |
| // so it can call our OnDeallocatePaths properly. |
| mReadClient = nullptr; |
| } |
| |
| app::BufferedReadCallback & GetBufferedCallback() { return mBufferedReadAdapter; } |
| |
| void AdoptReadClient(Platform::UniquePtr<app::ReadClient> aReadClient) { mReadClient = std::move(aReadClient); } |
| |
| private: |
| void OnAttributeData(const app::ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, |
| const app::StatusIB & aStatus) override |
| { |
| if (mCalledCallback && mReadClient->IsReadType()) |
| { |
| return; |
| } |
| mCalledCallback = true; |
| |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| DecodableAttributeType value; |
| |
| // |
| // We shouldn't be getting list item operations in the provided path since that should be handled by the buffered read |
| // callback. If we do, that's a bug. |
| // |
| VerifyOrDie(!aPath.IsListItemOperation()); |
| |
| VerifyOrExit(aStatus.IsSuccess(), err = aStatus.ToChipError()); |
| VerifyOrExit(aPath.mClusterId == mClusterId && aPath.mAttributeId == mAttributeId, err = CHIP_ERROR_SCHEMA_MISMATCH); |
| VerifyOrExit(apData != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT); |
| |
| SuccessOrExit(err = app::DataModel::Decode(*apData, value)); |
| |
| mOnSuccess(aPath, value); |
| |
| exit: |
| if (err != CHIP_NO_ERROR) |
| { |
| mOnError(&aPath, err); |
| } |
| } |
| |
| void OnError(CHIP_ERROR aError) override |
| { |
| if (mCalledCallback && mReadClient->IsReadType()) |
| { |
| return; |
| } |
| mCalledCallback = true; |
| |
| mOnError(nullptr, aError); |
| } |
| |
| void OnDone(app::ReadClient *) override { mOnDone(this); } |
| |
| void OnSubscriptionEstablished(SubscriptionId aSubscriptionId) override |
| { |
| if (mOnSubscriptionEstablished) |
| { |
| mOnSubscriptionEstablished(*mReadClient.get()); |
| } |
| } |
| |
| CHIP_ERROR OnResubscriptionNeeded(chip::app::ReadClient * apReadClient, CHIP_ERROR aTerminationCause) override |
| { |
| ReturnErrorOnFailure(app::ReadClient::Callback::OnResubscriptionNeeded(apReadClient, aTerminationCause)); |
| |
| if (mOnResubscriptionAttempt) |
| { |
| mOnResubscriptionAttempt(*mReadClient.get(), aTerminationCause, apReadClient->ComputeTimeTillNextSubscription()); |
| } |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| void OnDeallocatePaths(chip::app::ReadPrepareParams && aReadPrepareParams) override |
| { |
| VerifyOrDie(aReadPrepareParams.mAttributePathParamsListSize == 1 && |
| aReadPrepareParams.mpAttributePathParamsList != nullptr); |
| chip::Platform::Delete<app::AttributePathParams>(aReadPrepareParams.mpAttributePathParamsList); |
| |
| if (aReadPrepareParams.mDataVersionFilterListSize == 1 && aReadPrepareParams.mpDataVersionFilterList != nullptr) |
| { |
| chip::Platform::Delete<app::DataVersionFilter>(aReadPrepareParams.mpDataVersionFilterList); |
| } |
| } |
| |
| ClusterId mClusterId; |
| AttributeId mAttributeId; |
| OnSuccessCallbackType mOnSuccess; |
| OnErrorCallbackType mOnError; |
| OnDoneCallbackType mOnDone; |
| OnSubscriptionEstablishedCallbackType mOnSubscriptionEstablished; |
| OnResubscriptionAttemptCallbackType mOnResubscriptionAttempt; |
| app::BufferedReadCallback mBufferedReadAdapter; |
| Platform::UniquePtr<app::ReadClient> mReadClient; |
| // For reads, we ensure that we make only one data/error callback to our consumer. |
| bool mCalledCallback = false; |
| }; |
| |
| template <typename DecodableEventType> |
| class TypedReadEventCallback final : public app::ReadClient::Callback |
| { |
| public: |
| using OnSuccessCallbackType = std::function<void(const app::EventHeader & aEventHeader, const DecodableEventType & aData)>; |
| using OnErrorCallbackType = std::function<void(const app::EventHeader * apEventHeader, CHIP_ERROR aError)>; |
| using OnDoneCallbackType = std::function<void(app::ReadClient * apReadClient)>; |
| using OnSubscriptionEstablishedCallbackType = std::function<void(const app::ReadClient & aReadClient)>; |
| using OnResubscriptionAttemptCallbackType = |
| std::function<void(const app::ReadClient & aReadClient, CHIP_ERROR aError, uint32_t aNextResubscribeIntervalMsec)>; |
| |
| TypedReadEventCallback(OnSuccessCallbackType aOnSuccess, OnErrorCallbackType aOnError, OnDoneCallbackType aOnDone, |
| OnSubscriptionEstablishedCallbackType aOnSubscriptionEstablished = nullptr, |
| OnResubscriptionAttemptCallbackType aOnResubscriptionAttempt = nullptr) : |
| mOnSuccess(aOnSuccess), |
| mOnError(aOnError), mOnDone(aOnDone), mOnSubscriptionEstablished(aOnSubscriptionEstablished), |
| mOnResubscriptionAttempt(aOnResubscriptionAttempt) |
| {} |
| |
| ~TypedReadEventCallback() |
| { |
| // Ensure we release the ReadClient before we tear down anything else, |
| // so it can call our OnDeallocatePaths properly. |
| mReadClient = nullptr; |
| } |
| |
| void AdoptReadClient(Platform::UniquePtr<app::ReadClient> aReadClient) { mReadClient = std::move(aReadClient); } |
| |
| private: |
| void OnEventData(const app::EventHeader & aEventHeader, TLV::TLVReader * apData, const app::StatusIB * apStatus) override |
| { |
| if (mCalledCallback && mReadClient->IsReadType()) |
| { |
| return; |
| } |
| mCalledCallback = true; |
| |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| DecodableEventType value; |
| |
| // Only one of the apData and apStatus can be non-null, so apStatus will always indicate a failure status when it is not |
| // nullptr. |
| VerifyOrExit(apStatus == nullptr, err = apStatus->ToChipError()); |
| |
| VerifyOrExit(apData != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT); |
| |
| VerifyOrExit((aEventHeader.mPath.mEventId == value.GetEventId()) && (aEventHeader.mPath.mClusterId == value.GetClusterId()), |
| CHIP_ERROR_SCHEMA_MISMATCH); |
| err = app::DataModel::Decode(*apData, value); |
| SuccessOrExit(err); |
| |
| mOnSuccess(aEventHeader, value); |
| |
| exit: |
| if (err != CHIP_NO_ERROR) |
| { |
| mOnError(&aEventHeader, err); |
| } |
| } |
| |
| void OnError(CHIP_ERROR aError) override |
| { |
| if (mCalledCallback && mReadClient->IsReadType()) |
| { |
| return; |
| } |
| mCalledCallback = true; |
| |
| mOnError(nullptr, aError); |
| } |
| |
| void OnDone(app::ReadClient * apReadClient) override |
| { |
| if (mOnDone != nullptr) |
| { |
| mOnDone(apReadClient); |
| } |
| |
| // Always needs to be the last call |
| chip::Platform::Delete(this); |
| } |
| |
| void OnDeallocatePaths(chip::app::ReadPrepareParams && aReadPrepareParams) override |
| { |
| VerifyOrDie(aReadPrepareParams.mEventPathParamsListSize == 1 && aReadPrepareParams.mpEventPathParamsList != nullptr); |
| chip::Platform::Delete<app::EventPathParams>(aReadPrepareParams.mpEventPathParamsList); |
| } |
| |
| void OnSubscriptionEstablished(SubscriptionId aSubscriptionId) override |
| { |
| if (mOnSubscriptionEstablished) |
| { |
| mOnSubscriptionEstablished(*mReadClient.get()); |
| } |
| } |
| |
| CHIP_ERROR OnResubscriptionNeeded(chip::app::ReadClient * apReadClient, CHIP_ERROR aTerminationCause) override |
| { |
| ReturnErrorOnFailure(app::ReadClient::Callback::OnResubscriptionNeeded(apReadClient, aTerminationCause)); |
| |
| if (mOnResubscriptionAttempt) |
| { |
| mOnResubscriptionAttempt(*mReadClient.get(), aTerminationCause, apReadClient->ComputeTimeTillNextSubscription()); |
| } |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| OnSuccessCallbackType mOnSuccess; |
| OnErrorCallbackType mOnError; |
| OnDoneCallbackType mOnDone; |
| OnSubscriptionEstablishedCallbackType mOnSubscriptionEstablished; |
| OnResubscriptionAttemptCallbackType mOnResubscriptionAttempt; |
| Platform::UniquePtr<app::ReadClient> mReadClient; |
| // For reads, we ensure that we make only one data/error callback to our consumer. |
| bool mCalledCallback = false; |
| }; |
| |
| } // namespace Controller |
| } // namespace chip |