From 9c99d1ad7c13d98d0f2d3825cf9e6ab55b861181 Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Thu, 1 Sep 2022 11:26:15 -0400 Subject: [PATCH] Move identical parts of SubscriptionCallback into a shared base class. Fixes https://github.com/project-chip/connectedhomeip/issues/22322 --- src/darwin/Framework/CHIP/MTRBaseDevice.mm | 221 +----------------- .../CHIP/MTRBaseSubscriptionCallback.h | 150 ++++++++++++ .../CHIP/MTRBaseSubscriptionCallback.mm | 150 ++++++++++++ src/darwin/Framework/CHIP/MTRDevice.mm | 210 +---------------- .../Matter.xcodeproj/project.pbxproj | 8 + 5 files changed, 320 insertions(+), 419 deletions(-) create mode 100644 src/darwin/Framework/CHIP/MTRBaseSubscriptionCallback.h create mode 100644 src/darwin/Framework/CHIP/MTRBaseSubscriptionCallback.mm diff --git a/src/darwin/Framework/CHIP/MTRBaseDevice.mm b/src/darwin/Framework/CHIP/MTRBaseDevice.mm index a2e2cfad38b745..d323d15cd5c31e 100644 --- a/src/darwin/Framework/CHIP/MTRBaseDevice.mm +++ b/src/darwin/Framework/CHIP/MTRBaseDevice.mm @@ -18,11 +18,13 @@ #import "MTRAttributeCacheContainer_Internal.h" #import "MTRAttributeTLVValueDecoder_Internal.h" #import "MTRBaseDevice_Internal.h" +#import "MTRBaseSubscriptionCallback.h" #import "MTRCallbackBridgeBase_internal.h" #import "MTRCluster.h" #import "MTRError_Internal.h" #import "MTREventTLVValueDecoder_Internal.h" #import "MTRLogging.h" + #include "app/ConcreteAttributePath.h" #include "app/ConcreteCommandPath.h" #include "lib/core/CHIPError.h" @@ -39,8 +41,6 @@ #include -typedef void (^SubscriptionEstablishedHandler)(void); - using namespace chip; using namespace chip::app; using namespace chip::Protocols::InteractionModel; @@ -268,107 +268,21 @@ - (void)invalidateCASESession [self.deviceController invalidateCASESessionForNode:self.deviceID]; } -typedef void (^ReportCallback)(NSArray * _Nullable value, NSError * _Nullable error); -typedef void (^DataReportCallback)(NSArray * value); -typedef void (^ErrorCallback)(NSError * error); - namespace { -class SubscriptionCallback final : public ClusterStateCache::Callback { +class SubscriptionCallback final : public MTRBaseSubscriptionCallback { public: - SubscriptionCallback(dispatch_queue_t queue, DataReportCallback attributeReportCallback, DataReportCallback eventReportCallback, - ErrorCallback errorCallback, SubscriptionEstablishedHandler _Nullable subscriptionEstablishedHandler) - : mQueue(queue) - , mAttributeReportCallback(attributeReportCallback) - , mEventReportCallback(eventReportCallback) - , mErrorCallback(errorCallback) - , mSubscriptionEstablishedHandler(subscriptionEstablishedHandler) - , mBufferedReadAdapter(*this) - { - } - SubscriptionCallback(dispatch_queue_t queue, DataReportCallback attributeReportCallback, DataReportCallback eventReportCallback, ErrorCallback errorCallback, SubscriptionEstablishedHandler _Nullable subscriptionEstablishedHandler, - void (^onDoneHandler)(void)) - : mQueue(queue) - , mAttributeReportCallback(attributeReportCallback) - , mEventReportCallback(eventReportCallback) - , mErrorCallback(errorCallback) - , mSubscriptionEstablishedHandler(subscriptionEstablishedHandler) - , mBufferedReadAdapter(*this) - , mOnDoneHandler(onDoneHandler) - { - } - - ~SubscriptionCallback() + OnDoneHandler _Nullable onDoneHandler) + : MTRBaseSubscriptionCallback( + queue, attributeReportCallback, eventReportCallback, errorCallback, nil, subscriptionEstablishedHandler, onDoneHandler) { - // Ensure we release the ReadClient before we tear down anything else, - // so it can call our OnDeallocatePaths properly. - mReadClient = nullptr; } - BufferedReadCallback & GetBufferedCallback() { return mBufferedReadAdapter; } - - // We need to exist to get a ReadClient, so can't take this as a constructor argument. - void AdoptReadClient(std::unique_ptr aReadClient) { mReadClient = std::move(aReadClient); } - void AdoptAttributeCache(std::unique_ptr aAttributeCache) { mAttributeCache = std::move(aAttributeCache); } - -private: - void OnReportBegin() override; - - void OnReportEnd() override; - void OnEventData(const EventHeader & aEventHeader, TLV::TLVReader * apData, const StatusIB * apStatus) override; void OnAttributeData(const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, const StatusIB & aStatus) override; - - void OnError(CHIP_ERROR aError) override; - - void OnDone(ReadClient * aReadClient) override; - - void OnDeallocatePaths(ReadPrepareParams && aReadPrepareParams) override; - - void OnSubscriptionEstablished(SubscriptionId aSubscriptionId) override; - - void ReportData(); - - // Report an error, which may be due to issues in our own internal state or - // due to the OnError callback happening. - // - // aCancelSubscription should be false for the OnError case, since it will - // be immediately followed by OnDone and we want to do the deletion there. - void ReportError(CHIP_ERROR aError, bool aCancelSubscription = true); - -private: - dispatch_queue_t mQueue; - DataReportCallback _Nullable mAttributeReportCallback = nil; - DataReportCallback _Nullable mEventReportCallback = nil; - // We set mErrorCallback to nil when queueing error reports, so we - // make sure to only report one error. - ErrorCallback _Nullable mErrorCallback = nil; - SubscriptionEstablishedHandler _Nullable mSubscriptionEstablishedHandler; - BufferedReadCallback mBufferedReadAdapter; - NSMutableArray * _Nullable mAttributeReports = nil; - NSMutableArray * _Nullable mEventReports = nil; - - // Our lifetime management is a little complicated. On errors that don't - // originate with the ReadClient we attempt to delete ourselves (and hence - // the ReadClient), but asynchronously, because the ReadClient API doesn't - // allow sync deletion under callbacks other than OnDone. While that's - // pending, something else (e.g. an error it runs into) could end up calling - // OnDone on us. And generally if OnDone is called we want to delete - // ourselves as well. - // - // To handle this, enforce the following rules: - // - // 1) We guarantee that mErrorCallback is only invoked with an error once. - // 2) We ensure that we delete ourselves and the passed in ReadClient only from OnDone or a queued-up - // error callback, but not both, by tracking whether we have a queued-up - // deletion. - std::unique_ptr mReadClient; - std::unique_ptr mAttributeCache; - bool mHaveQueuedDeletion = false; - void (^mOnDoneHandler)(void) = nil; }; } // anonymous namespace @@ -433,7 +347,7 @@ - (void)subscribeWithQueue:(dispatch_queue_t)queue attributeCache->GetBufferedCallback(), ReadClient::InteractionType::Subscribe); } else { callback = std::make_unique(queue, attributeReportHandler, - eventReportHandler, errorHandler, subscriptionEstablishedHandler); + eventReportHandler, errorHandler, subscriptionEstablishedHandler, nil); readClient = std::make_unique(InteractionModelEngine::GetInstance(), exchangeManager, callback->GetBufferedCallback(), ReadClient::InteractionType::Subscribe); } @@ -1502,37 +1416,6 @@ - (instancetype)initWithPath:(const ConcreteEventPath &)path @end namespace { -void SubscriptionCallback::OnReportBegin() -{ - mAttributeReports = [NSMutableArray new]; - mEventReports = [NSMutableArray new]; -} - -// Reports attribute and event data if any exists -void SubscriptionCallback::ReportData() -{ - __block NSArray * attributeReports = mAttributeReports; - mAttributeReports = nil; - __block auto attributeCallback = mAttributeReportCallback; - - __block NSArray * eventReports = mEventReports; - mEventReports = nil; - __block auto eventCallback = mEventReportCallback; - - if (attributeCallback != nil && attributeReports.count) { - dispatch_async(mQueue, ^{ - attributeCallback(attributeReports); - }); - } - if (eventCallback != nil && eventReports.count) { - dispatch_async(mQueue, ^{ - eventCallback(eventReports); - }); - } -} - -void SubscriptionCallback::OnReportEnd() { ReportData(); } - void SubscriptionCallback::OnEventData(const EventHeader & aEventHeader, TLV::TLVReader * apData, const StatusIB * apStatus) { id _Nullable value = nil; @@ -1606,94 +1489,4 @@ - (instancetype)initWithPath:(const ConcreteEventPath &)path [mAttributeReports addObject:[[MTRAttributeReport alloc] initWithPath:aPath value:value error:error]]; } -void SubscriptionCallback::OnError(CHIP_ERROR aError) -{ - // If OnError is called after OnReportBegin, we should report the collected data - ReportData(); - ReportError(aError, /* aCancelSubscription = */ false); -} - -void SubscriptionCallback::OnDone(ReadClient *) -{ - if (mOnDoneHandler) { - mOnDoneHandler(); - mOnDoneHandler = nil; - } - if (!mHaveQueuedDeletion) { - delete this; - return; // Make sure we touch nothing else. - } -} - -void SubscriptionCallback::OnDeallocatePaths(ReadPrepareParams && aReadPrepareParams) -{ - VerifyOrDie((aReadPrepareParams.mAttributePathParamsListSize == 0 && aReadPrepareParams.mpAttributePathParamsList == nullptr) - || (aReadPrepareParams.mAttributePathParamsListSize == 1 && aReadPrepareParams.mpAttributePathParamsList != nullptr)); - if (aReadPrepareParams.mpAttributePathParamsList) { - delete aReadPrepareParams.mpAttributePathParamsList; - } - - VerifyOrDie((aReadPrepareParams.mDataVersionFilterListSize == 0 && aReadPrepareParams.mpDataVersionFilterList == nullptr) - || (aReadPrepareParams.mDataVersionFilterListSize == 1 && aReadPrepareParams.mpDataVersionFilterList != nullptr)); - if (aReadPrepareParams.mpDataVersionFilterList != nullptr) { - delete aReadPrepareParams.mpDataVersionFilterList; - } - - VerifyOrDie((aReadPrepareParams.mEventPathParamsListSize == 0 && aReadPrepareParams.mpEventPathParamsList == nullptr) - || (aReadPrepareParams.mEventPathParamsListSize == 1 && aReadPrepareParams.mpEventPathParamsList != nullptr)); - if (aReadPrepareParams.mpEventPathParamsList) { - delete aReadPrepareParams.mpEventPathParamsList; - } -} - -void SubscriptionCallback::OnSubscriptionEstablished(SubscriptionId aSubscriptionId) -{ - if (mSubscriptionEstablishedHandler) { - dispatch_async(mQueue, mSubscriptionEstablishedHandler); - } -} - -void SubscriptionCallback::ReportError(CHIP_ERROR aError, bool aCancelSubscription) -{ - auto * err = [MTRError errorForCHIPErrorCode:aError]; - if (!err) { - // Very strange... Someone tried to report a success status as an error? - return; - } - - if (mHaveQueuedDeletion) { - // Already have an error report pending which will delete us. - return; - } - - __block ErrorCallback callback = mErrorCallback; - __block auto * myself = this; - mErrorCallback = nil; - mAttributeReportCallback = nil; - mEventReportCallback = nil; - __auto_type onDoneHandler = mOnDoneHandler; - mOnDoneHandler = nil; - dispatch_async(mQueue, ^{ - callback(err); - if (onDoneHandler) { - onDoneHandler(); - } - }); - - if (aCancelSubscription) { - // We can't synchronously delete ourselves, because we're inside one of - // the ReadClient callbacks and we need to outlive the callback's - // execution. Queue an async deletion on the Matter queue (where we are - // running already). - // - // If we now get OnDone, we will ignore that, since we have the deletion - // posted already, but that's OK even during shutdown: since we are - // queueing the deletion now, it will be processed before the Matter queue - // gets paused, which is fairly early in the shutdown process. - mHaveQueuedDeletion = true; - dispatch_async(DeviceLayer::PlatformMgrImpl().GetWorkQueue(), ^{ - delete myself; - }); - } -} } // anonymous namespace diff --git a/src/darwin/Framework/CHIP/MTRBaseSubscriptionCallback.h b/src/darwin/Framework/CHIP/MTRBaseSubscriptionCallback.h new file mode 100644 index 00000000000000..856691f0f511f6 --- /dev/null +++ b/src/darwin/Framework/CHIP/MTRBaseSubscriptionCallback.h @@ -0,0 +1,150 @@ +/** + * Copyright (c) 2022 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. + */ + +#pragma once + +#import "Foundation/Foundation.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/** + * This file defines a base class for subscription callbacks used by + * MTRBaseDevice and MTRDevice. This base class handles everything except the + * actual conversion from the incoming data to the desired data. + * + * The desired data is assumed to be NSObjects that can be stored in NSArray. + */ + +NS_ASSUME_NONNULL_BEGIN + +typedef void (^DataReportCallback)(NSArray * value); +typedef void (^ErrorCallback)(NSError * error); +typedef void (^ResubscriptionCallback)(void); +typedef void (^SubscriptionEstablishedHandler)(void); +typedef void (^OnDoneHandler)(void); + +class MTRBaseSubscriptionCallback : public chip::app::ClusterStateCache::Callback { +public: + MTRBaseSubscriptionCallback(dispatch_queue_t queue, DataReportCallback attributeReportCallback, + DataReportCallback eventReportCallback, ErrorCallback errorCallback, + ResubscriptionCallback _Nullable resubscriptionCallback, + SubscriptionEstablishedHandler _Nullable subscriptionEstablishedHandler, OnDoneHandler _Nullable onDoneHandler) + : mQueue(queue) + , mAttributeReportCallback(attributeReportCallback) + , mEventReportCallback(eventReportCallback) + , mErrorCallback(errorCallback) + , mSubscriptionEstablishedHandler(subscriptionEstablishedHandler) + , mResubscriptionCallback(resubscriptionCallback) + , mBufferedReadAdapter(*this) + , mOnDoneHandler(onDoneHandler) + { + } + + virtual ~MTRBaseSubscriptionCallback() + { + // Ensure we release the ReadClient before we tear down anything else, + // so it can call our OnDeallocatePaths properly. + mReadClient = nullptr; + } + + chip::app::BufferedReadCallback & GetBufferedCallback() { return mBufferedReadAdapter; } + + // We need to exist to get a ReadClient, so can't take this as a constructor argument. + void AdoptReadClient(std::unique_ptr aReadClient) { mReadClient = std::move(aReadClient); } + void AdoptAttributeCache(std::unique_ptr aAttributeCache) + { + mAttributeCache = std::move(aAttributeCache); + } + +protected: + // Report an error, which may be due to issues in our own internal state or + // due to the OnError callback happening. + // + // aCancelSubscription should be false for the OnError case, since it will + // be immediately followed by OnDone and we want to do the deletion there. + void ReportError(CHIP_ERROR aError, bool aCancelSubscription = true); + +private: + void OnReportBegin() override; + + void OnReportEnd() override; + + // OnEventData and OnAttributeData must be implemented by subclasses. + void OnEventData(const chip::app::EventHeader & aEventHeader, chip::TLV::TLVReader * apData, + const chip::app::StatusIB * apStatus) override = 0; + + void OnAttributeData(const chip::app::ConcreteDataAttributePath & aPath, chip::TLV::TLVReader * apData, + const chip::app::StatusIB & aStatus) override = 0; + + void OnError(CHIP_ERROR aError) override; + + void OnDone(chip::app::ReadClient * aReadClient) override; + + void OnDeallocatePaths(chip::app::ReadPrepareParams && aReadPrepareParams) override; + + void OnSubscriptionEstablished(chip::SubscriptionId aSubscriptionId) override; + + CHIP_ERROR OnResubscriptionNeeded(chip::app::ReadClient * apReadClient, CHIP_ERROR aTerminationCause) override; + + void ReportData(); + +protected: + NSMutableArray * _Nullable mAttributeReports = nil; + NSMutableArray * _Nullable mEventReports = nil; + +private: + dispatch_queue_t mQueue; + DataReportCallback _Nullable mAttributeReportCallback = nil; + DataReportCallback _Nullable mEventReportCallback = nil; + // We set mErrorCallback to nil when queueing error reports, so we + // make sure to only report one error. + ErrorCallback _Nullable mErrorCallback = nil; + SubscriptionEstablishedHandler _Nullable mSubscriptionEstablishedHandler = nil; + ResubscriptionCallback _Nullable mResubscriptionCallback = nil; + chip::app::BufferedReadCallback mBufferedReadAdapter; + + // Our lifetime management is a little complicated. On errors that don't + // originate with the ReadClient we attempt to delete ourselves (and hence + // the ReadClient), but asynchronously, because the ReadClient API doesn't + // allow sync deletion under callbacks other than OnDone. While that's + // pending, something else (e.g. an error it runs into) could end up calling + // OnDone on us. And generally if OnDone is called we want to delete + // ourselves as well. + // + // To handle this, enforce the following rules: + // + // 1) We guarantee that mErrorCallback is only invoked with an error once. + // 2) We ensure that we delete ourselves and the passed in ReadClient only from OnDone or a queued-up + // error callback, but not both, by tracking whether we have a queued-up + // deletion. + std::unique_ptr mReadClient; + std::unique_ptr mAttributeCache; + bool mHaveQueuedDeletion = false; + OnDoneHandler _Nullable mOnDoneHandler = nil; +}; + +NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIP/MTRBaseSubscriptionCallback.mm b/src/darwin/Framework/CHIP/MTRBaseSubscriptionCallback.mm new file mode 100644 index 00000000000000..6882ec37b22f0f --- /dev/null +++ b/src/darwin/Framework/CHIP/MTRBaseSubscriptionCallback.mm @@ -0,0 +1,150 @@ +/** + * Copyright (c) 2022 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 "MTRBaseSubscriptionCallback.h" +#import "MTRError_Internal.h" + +#include + +using namespace chip; +using namespace chip::app; + +void MTRBaseSubscriptionCallback::OnReportBegin() +{ + mAttributeReports = [NSMutableArray new]; + mEventReports = [NSMutableArray new]; +} + +// Reports attribute and event data if any exists +void MTRBaseSubscriptionCallback::ReportData() +{ + __block NSArray * attributeReports = mAttributeReports; + mAttributeReports = nil; + __block auto attributeCallback = mAttributeReportCallback; + + __block NSArray * eventReports = mEventReports; + mEventReports = nil; + __block auto eventCallback = mEventReportCallback; + + if (attributeCallback != nil && attributeReports.count) { + dispatch_async(mQueue, ^{ + attributeCallback(attributeReports); + }); + } + if (eventCallback != nil && eventReports.count) { + dispatch_async(mQueue, ^{ + eventCallback(eventReports); + }); + } +} + +void MTRBaseSubscriptionCallback::OnReportEnd() { ReportData(); } + +void MTRBaseSubscriptionCallback::OnError(CHIP_ERROR aError) +{ + // If OnError is called after OnReportBegin, we should report the collected data + ReportData(); + ReportError(aError, /* aCancelSubscription = */ false); +} + +void MTRBaseSubscriptionCallback::OnDone(ReadClient *) +{ + if (mOnDoneHandler) { + mOnDoneHandler(); + mOnDoneHandler = nil; + } + if (!mHaveQueuedDeletion) { + delete this; + return; // Make sure we touch nothing else. + } +} + +void MTRBaseSubscriptionCallback::OnDeallocatePaths(ReadPrepareParams && aReadPrepareParams) +{ + VerifyOrDie((aReadPrepareParams.mAttributePathParamsListSize == 0 && aReadPrepareParams.mpAttributePathParamsList == nullptr) + || (aReadPrepareParams.mAttributePathParamsListSize == 1 && aReadPrepareParams.mpAttributePathParamsList != nullptr)); + if (aReadPrepareParams.mpAttributePathParamsList) { + delete aReadPrepareParams.mpAttributePathParamsList; + } + + VerifyOrDie((aReadPrepareParams.mDataVersionFilterListSize == 0 && aReadPrepareParams.mpDataVersionFilterList == nullptr) + || (aReadPrepareParams.mDataVersionFilterListSize == 1 && aReadPrepareParams.mpDataVersionFilterList != nullptr)); + if (aReadPrepareParams.mpDataVersionFilterList != nullptr) { + delete aReadPrepareParams.mpDataVersionFilterList; + } + + VerifyOrDie((aReadPrepareParams.mEventPathParamsListSize == 0 && aReadPrepareParams.mpEventPathParamsList == nullptr) + || (aReadPrepareParams.mEventPathParamsListSize == 1 && aReadPrepareParams.mpEventPathParamsList != nullptr)); + if (aReadPrepareParams.mpEventPathParamsList) { + delete aReadPrepareParams.mpEventPathParamsList; + } +} + +void MTRBaseSubscriptionCallback::OnSubscriptionEstablished(SubscriptionId aSubscriptionId) +{ + if (mSubscriptionEstablishedHandler) { + dispatch_async(mQueue, mSubscriptionEstablishedHandler); + } +} + +CHIP_ERROR MTRBaseSubscriptionCallback::OnResubscriptionNeeded(ReadClient * apReadClient, CHIP_ERROR aTerminationCause) +{ + return apReadClient->DefaultResubscribePolicy(aTerminationCause); +} + +void MTRBaseSubscriptionCallback::ReportError(CHIP_ERROR aError, bool aCancelSubscription) +{ + auto * err = [MTRError errorForCHIPErrorCode:aError]; + if (!err) { + // Very strange... Someone tried to report a success status as an error? + return; + } + + if (mHaveQueuedDeletion) { + // Already have an error report pending which will delete us. + return; + } + + __block ErrorCallback callback = mErrorCallback; + __block auto * myself = this; + mErrorCallback = nil; + mAttributeReportCallback = nil; + mEventReportCallback = nil; + __auto_type onDoneHandler = mOnDoneHandler; + mOnDoneHandler = nil; + dispatch_async(mQueue, ^{ + callback(err); + if (onDoneHandler) { + onDoneHandler(); + } + }); + + if (aCancelSubscription) { + // We can't synchronously delete ourselves, because we're inside one of + // the ReadClient callbacks and we need to outlive the callback's + // execution. Queue an async deletion on the Matter queue (where we are + // running already). + // + // If we now get OnDone, we will ignore that, since we have the deletion + // posted already, but that's OK even during shutdown: since we are + // queueing the deletion now, it will be processed before the Matter queue + // gets paused, which is fairly early in the shutdown process. + mHaveQueuedDeletion = true; + dispatch_async(DeviceLayer::PlatformMgrImpl().GetWorkQueue(), ^{ + delete myself; + }); + } +} diff --git a/src/darwin/Framework/CHIP/MTRDevice.mm b/src/darwin/Framework/CHIP/MTRDevice.mm index bf7565bf3ac0d3..2bfa2e62495cce 100644 --- a/src/darwin/Framework/CHIP/MTRDevice.mm +++ b/src/darwin/Framework/CHIP/MTRDevice.mm @@ -19,6 +19,7 @@ #import "MTRAsyncCallbackWorkQueue.h" #import "MTRBaseDevice_Internal.h" +#import "MTRBaseSubscriptionCallback.h" #import "MTRCluster.h" #import "MTRDeviceController_Internal.h" #import "MTRDevice_Internal.h" @@ -96,100 +97,22 @@ - (id)strongObject using namespace chip::app; using namespace chip::Protocols::InteractionModel; -typedef void (^DataReportCallback)(NSArray * value); -typedef void (^ErrorCallback)(NSError * error); -typedef void (^ResubscriptionCallback)(void); - namespace { -class SubscriptionCallback final : public ClusterStateCache::Callback { +class SubscriptionCallback final : public MTRBaseSubscriptionCallback { public: SubscriptionCallback(dispatch_queue_t queue, DataReportCallback attributeReportCallback, DataReportCallback eventReportCallback, ErrorCallback errorCallback, SubscriptionEstablishedHandler subscriptionEstablishedHandler, - ResubscriptionCallback resubscriptionCallback, void (^onDoneHandler)(void)) - : mQueue(queue) - , mAttributeReportCallback(attributeReportCallback) - , mEventReportCallback(eventReportCallback) - , mErrorCallback(errorCallback) - , mSubscriptionEstablishedHandler(subscriptionEstablishedHandler) - , mResubscriptionCallback(resubscriptionCallback) - , mBufferedReadAdapter(*this) - , mOnDoneHandler(onDoneHandler) - { - } - - ~SubscriptionCallback() + ResubscriptionCallback resubscriptionCallback, OnDoneHandler onDoneHandler) + : MTRBaseSubscriptionCallback(queue, attributeReportCallback, eventReportCallback, errorCallback, resubscriptionCallback, + subscriptionEstablishedHandler, onDoneHandler) { - // Ensure we release the ReadClient before we tear down anything else, - // so it can call our OnDeallocatePaths properly. - mReadClient = nullptr; } - BufferedReadCallback & GetBufferedCallback() { return mBufferedReadAdapter; } - - // We need to exist to get a ReadClient, so can't take this as a constructor argument. - void AdoptReadClient(std::unique_ptr aReadClient) { mReadClient = std::move(aReadClient); } - void AdoptAttributeCache(std::unique_ptr aAttributeCache) { mAttributeCache = std::move(aAttributeCache); } - private: - void OnReportBegin() override; - - void OnReportEnd() override; - void OnEventData(const EventHeader & aEventHeader, TLV::TLVReader * apData, const StatusIB * apStatus) override; void OnAttributeData(const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, const StatusIB & aStatus) override; - - void OnError(CHIP_ERROR aError) override; - - void OnDone(ReadClient * aReadClient) override; - - void OnDeallocatePaths(ReadPrepareParams && aReadPrepareParams) override; - - void OnSubscriptionEstablished(SubscriptionId aSubscriptionId) override; - - CHIP_ERROR OnResubscriptionNeeded(ReadClient * apReadClient, CHIP_ERROR aTerminationCause) override; - - void ReportData(); - - // Report an error, which may be due to issues in our own internal state or - // due to the OnError callback happening. - // - // aCancelSubscription should be false for the OnError case, since it will - // be immediately followed by OnDone and we want to do the deletion there. - void ReportError(CHIP_ERROR aError, bool aCancelSubscription = true); - -private: - dispatch_queue_t mQueue; - DataReportCallback _Nullable mAttributeReportCallback = nil; - DataReportCallback _Nullable mEventReportCallback = nil; - // We set mErrorCallback to nil when queueing error reports, so we - // make sure to only report one error. - ErrorCallback _Nullable mErrorCallback = nil; - SubscriptionEstablishedHandler _Nullable mSubscriptionEstablishedHandler; - ResubscriptionCallback mResubscriptionCallback; - BufferedReadCallback mBufferedReadAdapter; - NSMutableArray * _Nullable mAttributeReports = nil; - NSMutableArray * _Nullable mEventReports = nil; - - // Our lifetime management is a little complicated. On errors that don't - // originate with the ReadClient we attempt to delete ourselves (and hence - // the ReadClient), but asynchronously, because the ReadClient API doesn't - // allow sync deletion under callbacks other than OnDone. While that's - // pending, something else (e.g. an error it runs into) could end up calling - // OnDone on us. And generally if OnDone is called we want to delete - // ourselves as well. - // - // To handle this, enforce the following rules: - // - // 1) We guarantee that mErrorCallback is only invoked with an error once. - // 2) We ensure that we delete ourselves and the passed in ReadClient only from OnDone or a queued-up - // error callback, but not both, by tracking whether we have a queued-up - // deletion. - std::unique_ptr mReadClient; - std::unique_ptr mAttributeCache; - bool mHaveQueuedDeletion = false; - void (^mOnDoneHandler)(void) = nil; }; } // anonymous namespace @@ -756,33 +679,6 @@ - (void)setExpectedValues:(NSArray *> *)values expe #pragma mark - SubscriptionCallback namespace { -void SubscriptionCallback::OnReportBegin() -{ - mAttributeReports = [NSMutableArray new]; - mEventReports = [NSMutableArray new]; -} - -// Reports attribute and event data if any exists -void SubscriptionCallback::ReportData() -{ - __block NSArray * attributeReports = mAttributeReports; - mAttributeReports = nil; - __block NSArray * eventReports = mEventReports; - mEventReports = nil; - if (mAttributeReportCallback && attributeReports.count) { - dispatch_async(mQueue, ^{ - mAttributeReportCallback(attributeReports); - }); - } - if (mEventReportCallback && eventReports.count) { - dispatch_async(mQueue, ^{ - mEventReportCallback(eventReports); - }); - } -} - -void SubscriptionCallback::OnReportEnd() { ReportData(); } - void SubscriptionCallback::OnEventData(const EventHeader & aEventHeader, TLV::TLVReader * apData, const StatusIB * apStatus) { if (mEventReports == nil) { @@ -836,100 +732,4 @@ - (void)setExpectedValues:(NSArray *> *)values expe } } } - -void SubscriptionCallback::OnError(CHIP_ERROR aError) -{ - // If OnError is called after OnReportBegin, we should report the collected data - ReportData(); - ReportError(aError, /* aCancelSubscription = */ false); -} - -void SubscriptionCallback::OnDone(ReadClient *) -{ - if (mOnDoneHandler) { - mOnDoneHandler(); - mOnDoneHandler = nil; - } - if (!mHaveQueuedDeletion) { - delete this; - return; // Make sure we touch nothing else. - } -} - -void SubscriptionCallback::OnDeallocatePaths(ReadPrepareParams && aReadPrepareParams) -{ - VerifyOrDie((aReadPrepareParams.mAttributePathParamsListSize == 0 && aReadPrepareParams.mpAttributePathParamsList == nullptr) - || (aReadPrepareParams.mAttributePathParamsListSize == 1 && aReadPrepareParams.mpAttributePathParamsList != nullptr)); - if (aReadPrepareParams.mpAttributePathParamsList) { - delete aReadPrepareParams.mpAttributePathParamsList; - } - - VerifyOrDie((aReadPrepareParams.mDataVersionFilterListSize == 0 && aReadPrepareParams.mpDataVersionFilterList == nullptr) - || (aReadPrepareParams.mDataVersionFilterListSize == 1 && aReadPrepareParams.mpDataVersionFilterList != nullptr)); - if (aReadPrepareParams.mpDataVersionFilterList != nullptr) { - delete aReadPrepareParams.mpDataVersionFilterList; - } - - VerifyOrDie((aReadPrepareParams.mEventPathParamsListSize == 0 && aReadPrepareParams.mpEventPathParamsList == nullptr) - || (aReadPrepareParams.mEventPathParamsListSize == 1 && aReadPrepareParams.mpEventPathParamsList != nullptr)); - if (aReadPrepareParams.mpEventPathParamsList) { - delete aReadPrepareParams.mpEventPathParamsList; - } -} - -void SubscriptionCallback::OnSubscriptionEstablished(SubscriptionId aSubscriptionId) -{ - if (mSubscriptionEstablishedHandler) { - dispatch_async(mQueue, mSubscriptionEstablishedHandler); - } -} - -CHIP_ERROR SubscriptionCallback::OnResubscriptionNeeded(ReadClient * apReadClient, CHIP_ERROR aTerminationCause) -{ - return apReadClient->DefaultResubscribePolicy(aTerminationCause); -} - -void SubscriptionCallback::ReportError(CHIP_ERROR aError, bool aCancelSubscription) -{ - auto * err = [MTRError errorForCHIPErrorCode:aError]; - if (!err) { - // Very strange... Someone tried to create a MTRError for a success status? - return; - } - - if (mHaveQueuedDeletion) { - // Already have an error report pending which will delete us. - return; - } - - __block ErrorCallback callback = mErrorCallback; - __block auto * myself = this; - mErrorCallback = nil; - mAttributeReportCallback = nil; - mEventReportCallback = nil; - __auto_type onDoneHandler = mOnDoneHandler; - mOnDoneHandler = nil; - dispatch_async(mQueue, ^{ - callback(err); - if (onDoneHandler) { - onDoneHandler(); - } - }); - - if (aCancelSubscription) { - // We can't synchronously delete ourselves, because we're inside one of - // the ReadClient callbacks and we need to outlive the callback's - // execution. Queue an async deletion on the Matter queue (where we are - // running already). - // - // If we now get OnDone, we will ignore that, since we have the deletion - // posted already, but that's OK even during shutdown: since we are - // queueing the deletion now, it will be processed before the Matter queue - // gets paused, which is fairly early in the shutdown process. - mHaveQueuedDeletion = true; - dispatch_async(DeviceLayer::PlatformMgrImpl().GetWorkQueue(), ^{ - delete myself; - }); - } -} } // anonymous namespace diff --git a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj index 7102d13c7be998..17f34172908ec7 100644 --- a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj +++ b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj @@ -39,6 +39,8 @@ 3CF134AD289D8E570017A19E /* MTRAttestationInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 3CF134AC289D8E570017A19E /* MTRAttestationInfo.m */; }; 3CF134AF289D90FF0017A19E /* MTRNOCChainIssuer.h in Headers */ = {isa = PBXBuildFile; fileRef = 3CF134AE289D90FF0017A19E /* MTRNOCChainIssuer.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5112F606287CD2C100B827E7 /* privilege-storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5112F605287CD2C100B827E7 /* privilege-storage.cpp */; }; + 511913FB28C100EF009235E9 /* MTRBaseSubscriptionCallback.mm in Sources */ = {isa = PBXBuildFile; fileRef = 511913F928C100EF009235E9 /* MTRBaseSubscriptionCallback.mm */; }; + 511913FC28C100EF009235E9 /* MTRBaseSubscriptionCallback.h in Headers */ = {isa = PBXBuildFile; fileRef = 511913FA28C100EF009235E9 /* MTRBaseSubscriptionCallback.h */; }; 5129BCFD26A9EE3300122DDF /* MTRError.h in Headers */ = {isa = PBXBuildFile; fileRef = 5129BCFC26A9EE3300122DDF /* MTRError.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5136661328067D550025EDAE /* MTRDeviceController_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 5136660F28067D540025EDAE /* MTRDeviceController_Internal.h */; }; 5136661428067D550025EDAE /* MTRControllerFactory.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5136661028067D540025EDAE /* MTRControllerFactory.mm */; }; @@ -175,6 +177,8 @@ 3CF134AC289D8E570017A19E /* MTRAttestationInfo.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MTRAttestationInfo.m; sourceTree = ""; }; 3CF134AE289D90FF0017A19E /* MTRNOCChainIssuer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTRNOCChainIssuer.h; sourceTree = ""; }; 5112F605287CD2C100B827E7 /* privilege-storage.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "privilege-storage.cpp"; path = "../../../app/util/privilege-storage.cpp"; sourceTree = ""; }; + 511913F928C100EF009235E9 /* MTRBaseSubscriptionCallback.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRBaseSubscriptionCallback.mm; sourceTree = ""; }; + 511913FA28C100EF009235E9 /* MTRBaseSubscriptionCallback.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRBaseSubscriptionCallback.h; sourceTree = ""; }; 5129BCFC26A9EE3300122DDF /* MTRError.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTRError.h; sourceTree = ""; }; 5136660F28067D540025EDAE /* MTRDeviceController_Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRDeviceController_Internal.h; sourceTree = ""; }; 5136661028067D540025EDAE /* MTRControllerFactory.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRControllerFactory.mm; sourceTree = ""; }; @@ -442,6 +446,8 @@ 3CF134AA289D8DF70017A19E /* MTRAttestationInfo.h */, 3CF134AC289D8E570017A19E /* MTRAttestationInfo.m */, 3CF134AE289D90FF0017A19E /* MTRNOCChainIssuer.h */, + 511913FA28C100EF009235E9 /* MTRBaseSubscriptionCallback.h */, + 511913F928C100EF009235E9 /* MTRBaseSubscriptionCallback.mm */, ); path = CHIP; sourceTree = ""; @@ -540,6 +546,7 @@ 51E51FC0282AD37A00FC978D /* MTRDeviceControllerStartupParams_Internal.h in Headers */, 998F286F26D55EC5001846C6 /* MTRP256KeypairBridge.h in Headers */, 2C222ADF255C811800E446B9 /* MTRBaseDevice_Internal.h in Headers */, + 511913FC28C100EF009235E9 /* MTRBaseSubscriptionCallback.h in Headers */, 51E0310027EA20D20083DC9C /* MTRControllerAccessControl.h in Headers */, 7596A85628788557004DAE0E /* MTRClustersObjc_internal.h in Headers */, 991DC08B247704DC00C13860 /* MTRLogging.h in Headers */, @@ -697,6 +704,7 @@ 1ED276E026C57CF000547A89 /* MTRCallbackBridge.mm in Sources */, 517BF3F1282B62B800A8B7DB /* MTRCertificates.mm in Sources */, 5A6FEC9627B5983000F25F42 /* MTRDeviceControllerXPCConnection.m in Sources */, + 511913FB28C100EF009235E9 /* MTRBaseSubscriptionCallback.mm in Sources */, 5ACDDD7D27CD16D200EFD68A /* MTRAttributeCacheContainer.mm in Sources */, 513DDB8A2761F6F900DAA01A /* MTRAttributeTLVValueDecoder.mm in Sources */, 2FD775552695557E00FF4B12 /* error-mapping.cpp in Sources */,