blob: 65f4b51a11cd78ffd9aff438a22988acd1611a21 [file] [log] [blame]
/*
*
* 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