blob: b74c1855762dfa67d4f6d2dfde961e73d2e6bd47 [file] [log] [blame]
/*
* Copyright (c) 2022-2023 Project CHIP Authors
*
* 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.
*/
#import <Foundation/Foundation.h>
#import "MTRBaseDevice.h"
#import "MTRCallbackBridge.h"
#import "MTRCluster_Internal.h"
#include <app/CommandSender.h>
#include <app/ReadClient.h>
#include <app/data-model/NullObject.h>
#include <lib/core/DataModelTypes.h>
#include <lib/core/TLV.h>
#include <lib/support/CHIPMem.h>
#include <system/SystemClock.h>
NS_ASSUME_NONNULL_BEGIN
/**
* Utility functions base clusters use for doing reads and subscribes.
*/
template <typename BridgeType, typename DecodableAttributeType>
class MTRAttributeReportCallback : public chip::app::ReadClient::Callback {
public:
MTRAttributeReportCallback(BridgeType * _Nonnull bridge, typename BridgeType::SuccessCallbackType _Nonnull onAttributeReport,
MTRErrorCallback _Nonnull onError, chip::ClusterId clusterID, chip::AttributeId attributeID)
: mBridge(bridge)
, mOnAttributeReport(onAttributeReport)
, mOnError(onError)
, mClusterID(clusterID)
, mAttributeID(attributeID)
, mBufferedReadAdapter(*this)
{
}
~MTRAttributeReportCallback() {}
chip::app::BufferedReadCallback & GetBufferedCallback() { return mBufferedReadAdapter; }
void AdoptReadClient(chip::Platform::UniquePtr<chip::app::ReadClient> readClient) { mReadClient = std::move(readClient); }
protected:
void OnAttributeData(
const chip::app::ConcreteDataAttributePath & path, chip::TLV::TLVReader * data, const chip::app::StatusIB & status) 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(!path.IsListItemOperation());
VerifyOrExit(status.IsSuccess(), err = status.ToChipError());
VerifyOrExit(path.mClusterId == mClusterID && path.mAttributeId == mAttributeID, err = CHIP_ERROR_SCHEMA_MISMATCH);
VerifyOrExit(data != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT);
SuccessOrExit(err = chip::app::DataModel::Decode(*data, value));
mOnAttributeReport(mBridge, value);
exit:
if (err != CHIP_NO_ERROR) {
mOnError(mBridge, err);
}
}
void OnError(CHIP_ERROR error) override
{
if (mCalledCallback && mReadClient->IsReadType()) {
return;
}
mCalledCallback = true;
mOnError(mBridge, error);
}
void OnDone(chip::app::ReadClient *) override { chip::Platform::Delete(this); }
BridgeType * _Nonnull mBridge;
chip::ClusterId mClusterID;
chip::AttributeId mAttributeID;
typename BridgeType::SuccessCallbackType mOnAttributeReport;
MTRErrorCallback mOnError;
chip::app::BufferedReadCallback mBufferedReadAdapter;
chip::Platform::UniquePtr<chip::app::ReadClient> mReadClient;
// For reads, we ensure that we make only one data/error callback to our consumer.
bool mCalledCallback = false;
};
template <typename SubscriptionBridgeType, typename DecodableAttributeType>
class MTRAttributeSubscriptionCallback : public MTRAttributeReportCallback<SubscriptionBridgeType, DecodableAttributeType> {
public:
MTRAttributeSubscriptionCallback(SubscriptionBridgeType * _Nonnull bridge,
typename SubscriptionBridgeType::SuccessCallbackType onAttributeReport, MTRErrorCallback onError, chip::ClusterId clusterID,
chip::AttributeId attributeID)
: MTRAttributeReportCallback<SubscriptionBridgeType, DecodableAttributeType>(
bridge, onAttributeReport, onError, clusterID, attributeID)
{
}
~MTRAttributeSubscriptionCallback()
{
// Ensure we release the ReadClient before we tear down anything else,
// so it can call our OnDeallocatePaths properly.
this->mReadClient = nullptr;
}
private:
// The superclass OnResubscriptionNeeded is fine for our purposes.
void OnDeallocatePaths(chip::app::ReadPrepareParams && readPrepareParams) override
{
VerifyOrDie(readPrepareParams.mAttributePathParamsListSize == 1 && readPrepareParams.mpAttributePathParamsList != nullptr);
chip::Platform::Delete<chip::app::AttributePathParams>(readPrepareParams.mpAttributePathParamsList);
if (readPrepareParams.mDataVersionFilterListSize == 1 && readPrepareParams.mpDataVersionFilterList != nullptr) {
chip::Platform::Delete<chip::app::DataVersionFilter>(readPrepareParams.mpDataVersionFilterList);
}
}
void OnSubscriptionEstablished(chip::SubscriptionId subscriptionId) override { this->mBridge->OnSubscriptionEstablished(); }
void OnDone(chip::app::ReadClient * readClient) override
{
this->mBridge->OnDone();
MTRAttributeReportCallback<SubscriptionBridgeType, DecodableAttributeType>::OnDone(readClient);
}
};
template <typename DecodableAttributeType, typename BridgeType>
CHIP_ERROR MTRStartReadInteraction(BridgeType * _Nonnull bridge, MTRReadParams * params,
chip::Messaging::ExchangeManager & exchangeManager, const chip::SessionHandle & session,
typename BridgeType::SuccessCallbackType successCb, MTRErrorCallback failureCb, chip::EndpointId endpoint,
chip::ClusterId clusterID, chip::AttributeId attributeID)
{
auto readPaths = chip::Platform::MakeUnique<chip::app::AttributePathParams>(endpoint, clusterID, attributeID);
VerifyOrReturnError(readPaths != nullptr, CHIP_ERROR_NO_MEMORY);
chip::app::ReadPrepareParams readPrepareParams(session);
[params toReadPrepareParams:readPrepareParams];
readPrepareParams.mpAttributePathParamsList = readPaths.get();
readPrepareParams.mAttributePathParamsListSize = 1;
auto callback = chip::Platform::MakeUnique<MTRAttributeReportCallback<BridgeType, DecodableAttributeType>>(
bridge, successCb, failureCb, clusterID, attributeID);
VerifyOrReturnError(callback != nullptr, CHIP_ERROR_NO_MEMORY);
auto readClient = chip::Platform::MakeUnique<chip::app::ReadClient>(chip::app::InteractionModelEngine::GetInstance(),
&exchangeManager, callback->GetBufferedCallback(), chip::app::ReadClient::InteractionType::Read);
VerifyOrReturnError(readClient != nullptr, CHIP_ERROR_NO_MEMORY);
CHIP_ERROR err = readClient->SendRequest(readPrepareParams);
ReturnErrorOnFailure(err);
callback->AdoptReadClient(std::move(readClient));
callback.release();
return CHIP_NO_ERROR;
}
template <typename DecodableAttributeType, typename BridgeType>
CHIP_ERROR MTRStartSubscribeInteraction(BridgeType * _Nonnull bridge, MTRSubscribeParams * params,
chip::Messaging::ExchangeManager & exchangeManager, const chip::SessionHandle & session,
typename BridgeType::SuccessCallbackType successCb, MTRErrorCallback failureCb, chip::EndpointId endpoint,
chip::ClusterId clusterID, chip::AttributeId attributeID)
{
auto readPaths = chip::Platform::MakeUnique<chip::app::AttributePathParams>(endpoint, clusterID, attributeID);
VerifyOrReturnError(readPaths != nullptr, CHIP_ERROR_NO_MEMORY);
chip::app::ReadPrepareParams readPrepareParams(session);
[params toReadPrepareParams:readPrepareParams];
readPrepareParams.mpAttributePathParamsList = readPaths.get();
readPrepareParams.mAttributePathParamsListSize = 1;
auto callback = chip::Platform::MakeUnique<MTRAttributeSubscriptionCallback<BridgeType, DecodableAttributeType>>(
bridge, successCb, failureCb, clusterID, attributeID);
VerifyOrReturnError(callback != nullptr, CHIP_ERROR_NO_MEMORY);
auto readClient = chip::Platform::MakeUnique<chip::app::ReadClient>(chip::app::InteractionModelEngine::GetInstance(),
&exchangeManager, callback->GetBufferedCallback(), chip::app::ReadClient::InteractionType::Subscribe);
VerifyOrReturnError(readClient != nullptr, CHIP_ERROR_NO_MEMORY);
CHIP_ERROR err;
if (params.resubscribeAutomatically) {
readPaths.release();
err = readClient->SendAutoResubscribeRequest(std::move(readPrepareParams));
} else {
err = readClient->SendRequest(readPrepareParams);
}
ReturnErrorOnFailure(err);
bridge->KeepAliveOnCallback();
callback->AdoptReadClient(std::move(readClient));
callback.release();
return CHIP_NO_ERROR;
}
template <typename SubscriptionBridgeType, typename AttributeObjCType, typename DecodableAttributeType>
void MTRSubscribeAttribute(MTRSubscribeParams * _Nonnull params,
MTRSubscriptionEstablishedHandler _Nullable subscriptionEstablished,
void (^reportHandler)(AttributeObjCType * _Nullable value, NSError * _Nullable error), dispatch_queue_t callbackQueue,
MTRBaseDevice * device, chip::EndpointId endpoint, chip::ClusterId clusterID, chip::AttributeId attributeID)
{
// Make a copy of params before we go async.
params = [params copy];
auto * callbackBridge = new SubscriptionBridgeType(
callbackQueue,
// This treats reportHandler as taking an id for the data. This is
// not great from a type-safety perspective, of course.
reportHandler,
^(chip::Messaging::ExchangeManager & exchangeManager, const chip::SessionHandle & session,
typename SubscriptionBridgeType::SuccessCallbackType successCb, MTRErrorCallback failureCb,
MTRCallbackBridgeBase * bridge) {
auto * subscriptionBridge = static_cast<SubscriptionBridgeType *>(bridge);
return MTRStartSubscribeInteraction<DecodableAttributeType>(
subscriptionBridge, params, exchangeManager, session, successCb, failureCb, endpoint, clusterID, attributeID);
},
subscriptionEstablished);
std::move(*callbackBridge).DispatchAction(device);
}
template <typename ReadBridgeType, typename AttributeObjCType, typename DecodableAttributeType>
void MTRReadAttribute(MTRReadParams * _Nonnull params,
void (^reportHandler)(AttributeObjCType * _Nullable value, NSError * _Nullable error), dispatch_queue_t callbackQueue,
MTRBaseDevice * device, chip::EndpointId endpoint, chip::ClusterId clusterID, chip::AttributeId attributeID)
{
// Make a copy of params before we go async.
params = [params copy];
auto * callbackBridge = new ReadBridgeType(callbackQueue,
// This treats reportHandler as taking an id for the data. This is
// not great from a type-safety perspective, of course.
reportHandler,
^(chip::Messaging::ExchangeManager & exchangeManager, const chip::SessionHandle & session,
typename ReadBridgeType::SuccessCallbackType successCb, MTRErrorCallback failureCb, MTRCallbackBridgeBase * bridge) {
auto * readBridge = static_cast<ReadBridgeType *>(bridge);
return MTRStartReadInteraction<DecodableAttributeType>(
readBridge, params, exchangeManager, session, successCb, failureCb, endpoint, clusterID, attributeID);
});
std::move(*callbackBridge).DispatchAction(device);
}
/**
* Utility functions base clusters use for doing commands.
*/
template <typename InvokeBridgeType, typename ResponseType>
class MTRInvokeCallback : public chip::app::CommandSender::Callback {
public:
MTRInvokeCallback(InvokeBridgeType * _Nonnull bridge, typename InvokeBridgeType::SuccessCallbackType _Nonnull onResponse,
MTRErrorCallback _Nonnull onError)
: mBridge(bridge)
, mOnResponse(onResponse)
, mOnError(onError)
{
}
~MTRInvokeCallback() {}
void AdoptCommandSender(chip::Platform::UniquePtr<chip::app::CommandSender> commandSender)
{
mCommandSender = std::move(commandSender);
}
protected:
// We need to have different OnResponse implementations depending on whether
// ResponseType is DataModel::NullObjectType or not. Since template class methods
// can't be partially specialized (either you have to partially specialize
// the class template, or you have to fully specialize the method), use
// enable_if to deal with this.
void OnResponse(chip::app::CommandSender * commandSender, const chip::app::ConcreteCommandPath & commandPath,
const chip::app::StatusIB & status, chip::TLV::TLVReader * reader) override
{
HandleResponse(commandSender, commandPath, status, reader);
}
/**
* Response handler for data responses.
*/
template <typename T = ResponseType, std::enable_if_t<!std::is_same<T, chip::app::DataModel::NullObjectType>::value, int> = 0>
void HandleResponse(chip::app::CommandSender * commandSender, const chip::app::ConcreteCommandPath & commandPath,
const chip::app::StatusIB & status, chip::TLV::TLVReader * reader)
{
if (mCalledCallback) {
return;
}
mCalledCallback = true;
ResponseType response;
CHIP_ERROR err = CHIP_NO_ERROR;
//
// We're expecting response data in this variant of OnResponse. Consequently, reader should always be
// non-null. If it is, it means we received a success status code instead, which is not what was expected.
//
VerifyOrExit(reader != nullptr, err = CHIP_ERROR_SCHEMA_MISMATCH);
//
// Validate that the data response we received matches what we expect in terms of its cluster and command IDs.
//
VerifyOrExit(
commandPath.mClusterId == ResponseType::GetClusterId() && commandPath.mCommandId == ResponseType::GetCommandId(),
err = CHIP_ERROR_SCHEMA_MISMATCH);
err = chip::app::DataModel::Decode(*reader, response);
SuccessOrExit(err);
mOnResponse(mBridge, response);
exit:
if (err != CHIP_NO_ERROR) {
mOnError(mBridge, err);
}
}
/**
* Response handler for status responses.
*/
template <typename T = ResponseType, std::enable_if_t<std::is_same<T, chip::app::DataModel::NullObjectType>::value, int> = 0>
void HandleResponse(chip::app::CommandSender * commandSender, const chip::app::ConcreteCommandPath & commandPath,
const chip::app::StatusIB & status, chip::TLV::TLVReader * reader)
{
if (mCalledCallback) {
return;
}
mCalledCallback = true;
//
// If we got a valid reader, it means we received response data that we were not expecting to receive.
//
if (reader != nullptr) {
mOnError(mBridge, CHIP_ERROR_SCHEMA_MISMATCH);
return;
}
chip::app::DataModel::NullObjectType nullResp;
mOnResponse(mBridge, nullResp);
}
void OnError(const chip::app::CommandSender * commandSender, CHIP_ERROR error) override
{
if (mCalledCallback) {
return;
}
mCalledCallback = true;
mOnError(mBridge, error);
}
void OnDone(chip::app::CommandSender * commandSender) override
{
if (!mCalledCallback) {
// This can happen if the server sends a response with an empty
// InvokeResponses list. Since we are not sending wildcard command
// paths, that's not a valid response and we should treat it as an
// error. Use the error we would have gotten if we in fact expected
// a nonempty list.
OnError(commandSender, CHIP_END_OF_TLV);
}
chip::Platform::Delete(this);
}
InvokeBridgeType * _Nonnull mBridge;
typename InvokeBridgeType::SuccessCallbackType mOnResponse;
MTRErrorCallback mOnError;
chip::Platform::UniquePtr<chip::app::CommandSender> mCommandSender;
// For reads, we ensure that we make only one data/error callback to our consumer.
bool mCalledCallback = false;
};
/**
* timedInvokeTimeoutMs, if provided, is how long the server will wait for us to
* send the invoke after we sent the Timed Request message.
*
* invokeTimeout, if provided, will have possible MRP latency added to it and
* the result is how long we will wait for the server to respond.
*/
template <typename BridgeType, typename RequestDataType>
CHIP_ERROR MTRStartInvokeInteraction(BridgeType * _Nonnull bridge, const RequestDataType & requestData,
chip::Messaging::ExchangeManager & exchangeManager, const chip::SessionHandle & session,
typename BridgeType::SuccessCallbackType successCb, MTRErrorCallback failureCb, chip::EndpointId endpoint,
chip::Optional<uint16_t> timedInvokeTimeoutMs, chip::Optional<chip::System::Clock::Timeout> invokeTimeout)
{
auto callback = chip::Platform::MakeUnique<MTRInvokeCallback<BridgeType, typename RequestDataType::ResponseType>>(
bridge, successCb, failureCb);
VerifyOrReturnError(callback != nullptr, CHIP_ERROR_NO_MEMORY);
auto commandSender
= chip::Platform::MakeUnique<chip::app::CommandSender>(callback.get(), &exchangeManager, timedInvokeTimeoutMs.HasValue());
VerifyOrReturnError(commandSender != nullptr, CHIP_ERROR_NO_MEMORY);
chip::app::CommandPathParams commandPath(endpoint, 0, RequestDataType::GetClusterId(), RequestDataType::GetCommandId(),
chip::app::CommandPathFlags::kEndpointIdValid);
ReturnErrorOnFailure(commandSender->AddRequestData(commandPath, requestData, timedInvokeTimeoutMs));
if (invokeTimeout.HasValue()) {
invokeTimeout.SetValue(session->ComputeRoundTripTimeout(invokeTimeout.Value()));
}
ReturnErrorOnFailure(commandSender->SendCommandRequest(session, invokeTimeout));
callback->AdoptCommandSender(std::move(commandSender));
callback.release();
return CHIP_NO_ERROR;
};
NS_ASSUME_NONNULL_END