Skip to content

Commit

Permalink
Add a Darwin framework notification when subscription drops. (#22351)
Browse files Browse the repository at this point in the history
* Add a Darwin framework notification when subscription drops.

Tells the API consumer the reason for the drop and how long we will wait before resubscribing.

Fixes #21613

* Address review comment.
  • Loading branch information
bzbarsky-apple authored and pull[bot] committed Aug 25, 2023
1 parent acdda77 commit 2299664
Show file tree
Hide file tree
Showing 11 changed files with 141 additions and 99 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,9 @@ class SubscribeEvent : public ModelCommand {
}
subscriptionEstablished:^() {
mSubscriptionEstablished = YES;
}
resubscriptionScheduled:^(NSError * error, NSNumber * resubscriptionDelay) {
NSLog(@"Subscription dropped with error %@. Resubscription in %@ms", error, resubscriptionDelay);
}];

return CHIP_NO_ERROR;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,8 +250,9 @@ - (void)reportFromUserEnteredSettings
errorHandler:^(NSError * error) {
NSLog(@"Status: update reportAttributeMeasuredValue completed with error %@", [error description]);
}
subscriptionEstablished:^ {
}];
subscriptionEstablished:^{
}
resubscriptionScheduled:nil];
} else {
NSLog(@"Status: Failed to establish a connection with the device");
}
Expand Down
27 changes: 23 additions & 4 deletions src/darwin/Framework/CHIP/MTRBaseDevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,16 @@ typedef void (^MTRDeviceResponseHandler)(NSArray<NSDictionary<NSString *, id> *>
typedef void (^MTRDeviceReportHandler)(NSArray * values);
typedef void (^MTRDeviceErrorHandler)(NSError * error);

/**
* Handler for subscribeWithQueue: resubscription scheduling notifications.
* This will be called when subscription loss is detected.
*
* @param error An error indicating the reason the subscription has been lost.
* @param resubscriptionDelay A delay, in milliseconds, before the next
* automatic resubscription will be attempted.
*/
typedef void (^MTRDeviceResubscriptionScheduledHandler)(NSError * error, NSNumber * resubscriptionDelay);

extern NSString * const MTRAttributePathKey;
extern NSString * const MTRCommandPathKey;
extern NSString * const MTREventPathKey;
Expand Down Expand Up @@ -126,15 +136,23 @@ extern NSString * const MTRArrayValueType;
* instances. Errors for specific paths, not the whole subscription, will be
* reported via those objects.
*
* errorHandler will be called any time there is an error for the
* entire subscription (with a non-nil "error"), and terminate the subscription.
* errorHandler will be called any time there is an error for the entire
* subscription (with a non-nil "error"), and terminate the subscription. This
* will generally not be invoked if auto-resubscription is enabled, unless there
* is a fatal error during a resubscription attempt.
*
* Both report handlers are not supported over XPC at the moment.
*
* subscriptionEstablished block, if not nil, will be called once the
* The subscriptionEstablished block, if not nil, will be called once the
* subscription is established. This will be _after_ the first (priming) call
* to both report handlers. Note that if the MTRSubscribeParams are set to
* automatically resubscribe this can end up being called more than once.
*
* The resubscriptionScheduled block, if not nil, will be called if
* auto-resubscription is enabled, subscription loss is detected, and a
* resubscription is scheduled. This can be called multiple times in a row
* without an intervening subscriptionEstablished call if the resubscription
* attempts fail.
*/
- (void)subscribeWithQueue:(dispatch_queue_t)queue
minInterval:(uint16_t)minInterval
Expand All @@ -144,7 +162,8 @@ extern NSString * const MTRArrayValueType;
attributeReportHandler:(MTRDeviceReportHandler _Nullable)attributeReportHandler
eventReportHandler:(MTRDeviceReportHandler _Nullable)eventReportHandler
errorHandler:(MTRDeviceErrorHandler)errorHandler
subscriptionEstablished:(dispatch_block_t _Nullable)subscriptionEstablishedHandler;
subscriptionEstablished:(dispatch_block_t _Nullable)subscriptionEstablishedHandler
resubscriptionScheduled:(MTRDeviceResubscriptionScheduledHandler _Nullable)resubscriptionScheduledHandler;

/**
* Read attribute in a designated attribute path
Expand Down
154 changes: 78 additions & 76 deletions src/darwin/Framework/CHIP/MTRBaseDevice.mm
Original file line number Diff line number Diff line change
Expand Up @@ -273,10 +273,10 @@ - (void)invalidateCASESession
class SubscriptionCallback final : public MTRBaseSubscriptionCallback {
public:
SubscriptionCallback(dispatch_queue_t queue, DataReportCallback attributeReportCallback, DataReportCallback eventReportCallback,
ErrorCallback errorCallback, SubscriptionEstablishedHandler _Nullable subscriptionEstablishedHandler,
OnDoneHandler _Nullable onDoneHandler)
: MTRBaseSubscriptionCallback(
queue, attributeReportCallback, eventReportCallback, errorCallback, nil, subscriptionEstablishedHandler, onDoneHandler)
ErrorCallback errorCallback, MTRDeviceResubscriptionScheduledHandler _Nullable resubscriptionScheduledHandler,
SubscriptionEstablishedHandler _Nullable subscriptionEstablishedHandler, OnDoneHandler _Nullable onDoneHandler)
: MTRBaseSubscriptionCallback(queue, attributeReportCallback, eventReportCallback, errorCallback,
resubscriptionScheduledHandler, subscriptionEstablishedHandler, onDoneHandler)
{
}

Expand All @@ -296,6 +296,7 @@ - (void)subscribeWithQueue:(dispatch_queue_t)queue
eventReportHandler:(nullable void (^)(NSArray * value))eventReportHandler
errorHandler:(void (^)(NSError * error))errorHandler
subscriptionEstablished:(nullable void (^)(void))subscriptionEstablishedHandler
resubscriptionScheduled:(MTRDeviceResubscriptionScheduledHandler _Nullable)resubscriptionScheduledHandler
{
if (self.paseDevice != nil) {
// We don't support subscriptions over PASE.
Expand All @@ -308,78 +309,79 @@ - (void)subscribeWithQueue:(dispatch_queue_t)queue
// Copy params before going async.
params = [params copy];

[self.deviceController getSessionForNode:self.nodeID
completionHandler:^(ExchangeManager * _Nullable exchangeManager, const Optional<SessionHandle> & session,
NSError * _Nullable error) {
if (error != nil) {
dispatch_async(queue, ^{
errorHandler(error);
});
return;
}

// Wildcard endpoint, cluster, attribute, event.
auto attributePath = std::make_unique<AttributePathParams>();
auto eventPath = std::make_unique<EventPathParams>();
ReadPrepareParams readParams(session.Value());
readParams.mMinIntervalFloorSeconds = minInterval;
readParams.mMaxIntervalCeilingSeconds = maxInterval;
readParams.mpAttributePathParamsList = attributePath.get();
readParams.mAttributePathParamsListSize = 1;
readParams.mpEventPathParamsList = eventPath.get();
readParams.mEventPathParamsListSize = 1;
readParams.mKeepSubscriptions = [params.keepPreviousSubscriptions boolValue];

std::unique_ptr<SubscriptionCallback> callback;
std::unique_ptr<ReadClient> readClient;
std::unique_ptr<ClusterStateCache> attributeCache;
if (attributeCacheContainer) {
__weak MTRAttributeCacheContainer * weakPtr = attributeCacheContainer;
callback = std::make_unique<SubscriptionCallback>(queue, attributeReportHandler,
eventReportHandler, errorHandler, subscriptionEstablishedHandler, ^{
MTRAttributeCacheContainer * container = weakPtr;
if (container) {
container.cppAttributeCache = nullptr;
}
});
attributeCache = std::make_unique<ClusterStateCache>(*callback.get());
readClient = std::make_unique<ReadClient>(InteractionModelEngine::GetInstance(), exchangeManager,
attributeCache->GetBufferedCallback(), ReadClient::InteractionType::Subscribe);
} else {
callback = std::make_unique<SubscriptionCallback>(queue, attributeReportHandler,
eventReportHandler, errorHandler, subscriptionEstablishedHandler, nil);
readClient = std::make_unique<ReadClient>(InteractionModelEngine::GetInstance(), exchangeManager,
callback->GetBufferedCallback(), ReadClient::InteractionType::Subscribe);
}

CHIP_ERROR err;
if (params != nil && params.autoResubscribe != nil && ![params.autoResubscribe boolValue]) {
err = readClient->SendRequest(readParams);
} else {
// SendAutoResubscribeRequest cleans up the params, even on failure.
attributePath.release();
eventPath.release();
err = readClient->SendAutoResubscribeRequest(std::move(readParams));
}

if (err != CHIP_NO_ERROR) {
dispatch_async(queue, ^{
errorHandler([MTRError errorForCHIPErrorCode:err]);
});

return;
}

if (attributeCacheContainer) {
attributeCacheContainer.cppAttributeCache = attributeCache.get();
// ClusterStateCache will be deleted when OnDone is called or an error is encountered as well.
callback->AdoptAttributeCache(std::move(attributeCache));
}
// Callback and ReadClient will be deleted when OnDone is called or an error is
// encountered.
callback->AdoptReadClient(std::move(readClient));
callback.release();
}];
[self.deviceController
getSessionForNode:self.nodeID
completionHandler:^(
ExchangeManager * _Nullable exchangeManager, const Optional<SessionHandle> & session, NSError * _Nullable error) {
if (error != nil) {
dispatch_async(queue, ^{
errorHandler(error);
});
return;
}

// Wildcard endpoint, cluster, attribute, event.
auto attributePath = std::make_unique<AttributePathParams>();
auto eventPath = std::make_unique<EventPathParams>();
ReadPrepareParams readParams(session.Value());
readParams.mMinIntervalFloorSeconds = minInterval;
readParams.mMaxIntervalCeilingSeconds = maxInterval;
readParams.mpAttributePathParamsList = attributePath.get();
readParams.mAttributePathParamsListSize = 1;
readParams.mpEventPathParamsList = eventPath.get();
readParams.mEventPathParamsListSize = 1;
readParams.mKeepSubscriptions = [params.keepPreviousSubscriptions boolValue];

std::unique_ptr<SubscriptionCallback> callback;
std::unique_ptr<ReadClient> readClient;
std::unique_ptr<ClusterStateCache> attributeCache;
if (attributeCacheContainer) {
__weak MTRAttributeCacheContainer * weakPtr = attributeCacheContainer;
callback = std::make_unique<SubscriptionCallback>(queue, attributeReportHandler, eventReportHandler, errorHandler,
resubscriptionScheduledHandler, subscriptionEstablishedHandler, ^{
MTRAttributeCacheContainer * container = weakPtr;
if (container) {
container.cppAttributeCache = nullptr;
}
});
attributeCache = std::make_unique<ClusterStateCache>(*callback.get());
readClient = std::make_unique<ReadClient>(InteractionModelEngine::GetInstance(), exchangeManager,
attributeCache->GetBufferedCallback(), ReadClient::InteractionType::Subscribe);
} else {
callback = std::make_unique<SubscriptionCallback>(queue, attributeReportHandler, eventReportHandler, errorHandler,
resubscriptionScheduledHandler, subscriptionEstablishedHandler, nil);
readClient = std::make_unique<ReadClient>(InteractionModelEngine::GetInstance(), exchangeManager,
callback->GetBufferedCallback(), ReadClient::InteractionType::Subscribe);
}

CHIP_ERROR err;
if (params != nil && params.autoResubscribe != nil && ![params.autoResubscribe boolValue]) {
err = readClient->SendRequest(readParams);
} else {
// SendAutoResubscribeRequest cleans up the params, even on failure.
attributePath.release();
eventPath.release();
err = readClient->SendAutoResubscribeRequest(std::move(readParams));
}

if (err != CHIP_NO_ERROR) {
dispatch_async(queue, ^{
errorHandler([MTRError errorForCHIPErrorCode:err]);
});

return;
}

if (attributeCacheContainer) {
attributeCacheContainer.cppAttributeCache = attributeCache.get();
// ClusterStateCache will be deleted when OnDone is called or an error is encountered as well.
callback->AdoptAttributeCache(std::move(attributeCache));
}
// Callback and ReadClient will be deleted when OnDone is called or an error is
// encountered.
callback->AdoptReadClient(std::move(readClient));
callback.release();
}];
}

// Convert TLV data into data-value dictionary as described in MTRDeviceResponseHandler
Expand Down
8 changes: 4 additions & 4 deletions src/darwin/Framework/CHIP/MTRBaseSubscriptionCallback.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#pragma once

#import "Foundation/Foundation.h"
#import "MTRBaseDevice.h"

#include <app/BufferedReadCallback.h>
#include <app/ClusterStateCache.h>
Expand All @@ -43,22 +44,21 @@ 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,
MTRDeviceResubscriptionScheduledHandler _Nullable resubscriptionCallback,
SubscriptionEstablishedHandler _Nullable subscriptionEstablishedHandler, OnDoneHandler _Nullable onDoneHandler)
: mQueue(queue)
, mAttributeReportCallback(attributeReportCallback)
, mEventReportCallback(eventReportCallback)
, mErrorCallback(errorCallback)
, mSubscriptionEstablishedHandler(subscriptionEstablishedHandler)
, mResubscriptionCallback(resubscriptionCallback)
, mSubscriptionEstablishedHandler(subscriptionEstablishedHandler)
, mBufferedReadAdapter(*this)
, mOnDoneHandler(onDoneHandler)
{
Expand Down Expand Up @@ -123,8 +123,8 @@ class MTRBaseSubscriptionCallback : public chip::app::ClusterStateCache::Callbac
// We set mErrorCallback to nil when queueing error reports, so we
// make sure to only report one error.
ErrorCallback _Nullable mErrorCallback = nil;
MTRDeviceResubscriptionScheduledHandler _Nullable mResubscriptionCallback = 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
Expand Down
13 changes: 12 additions & 1 deletion src/darwin/Framework/CHIP/MTRBaseSubscriptionCallback.mm
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,18 @@

CHIP_ERROR MTRBaseSubscriptionCallback::OnResubscriptionNeeded(ReadClient * apReadClient, CHIP_ERROR aTerminationCause)
{
return apReadClient->DefaultResubscribePolicy(aTerminationCause);
CHIP_ERROR err = ClusterStateCache::Callback::OnResubscriptionNeeded(apReadClient, aTerminationCause);
ReturnErrorOnFailure(err);

if (mResubscriptionCallback != nil) {
auto callback = mResubscriptionCallback;
auto error = [MTRError errorForCHIPErrorCode:aTerminationCause];
auto delayMs = @(apReadClient->ComputeTimeTillNextSubscription());
dispatch_async(mQueue, ^{
callback(error, delayMs);
});
}
return CHIP_NO_ERROR;
}

void MTRBaseSubscriptionCallback::ReportError(CHIP_ERROR aError, bool aCancelSubscription)
Expand Down
12 changes: 6 additions & 6 deletions src/darwin/Framework/CHIP/MTRDevice.mm
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ - (id)strongObject
class SubscriptionCallback final : public MTRBaseSubscriptionCallback {
public:
SubscriptionCallback(dispatch_queue_t queue, DataReportCallback attributeReportCallback, DataReportCallback eventReportCallback,
ErrorCallback errorCallback, SubscriptionEstablishedHandler subscriptionEstablishedHandler,
ResubscriptionCallback resubscriptionCallback, OnDoneHandler onDoneHandler)
ErrorCallback errorCallback, MTRDeviceResubscriptionScheduledHandler resubscriptionCallback,
SubscriptionEstablishedHandler subscriptionEstablishedHandler, OnDoneHandler onDoneHandler)
: MTRBaseSubscriptionCallback(queue, attributeReportCallback, eventReportCallback, errorCallback, resubscriptionCallback,
subscriptionEstablishedHandler, onDoneHandler)
{
Expand Down Expand Up @@ -327,14 +327,14 @@ - (void)subscribeWithMinInterval:(uint16_t)minInterval maxInterval:(uint16_t)max
// OnError
[self _handleSubscriptionError:error];
},
^(NSError * error, NSNumber * resubscriptionDelay) {
// OnResubscriptionNeeded
[self _handleResubscriptionNeeded];
},
^(void) {
// OnSubscriptionEstablished
[self _handleSubscriptionEstablished];
},
^(void) {
// OnResubscriptionNeeded
[self _handleResubscriptionNeeded];
},
^(void) {
// OnDone
[self _handleSubscriptionReset];
Expand Down
Loading

0 comments on commit 2299664

Please sign in to comment.