Skip to content

Commit

Permalink
Add a Darwin framework notification when subscription drops.
Browse files Browse the repository at this point in the history
Tells the API consumer the reason for the drop and how long we will wait before resubscribing.

Fixes #21613
  • Loading branch information
bzbarsky-apple committed Sep 1, 2022
1 parent b628eef commit 65f28f0
Show file tree
Hide file tree
Showing 11 changed files with 136 additions and 94 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,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
6 changes: 3 additions & 3 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,15 +44,14 @@ 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)
Expand Down Expand Up @@ -124,7 +124,7 @@ class MTRBaseSubscriptionCallback : public chip::app::ClusterStateCache::Callbac
// make sure to only report one error.
ErrorCallback _Nullable mErrorCallback = nil;
SubscriptionEstablishedHandler _Nullable mSubscriptionEstablishedHandler = nil;
ResubscriptionCallback _Nullable mResubscriptionCallback = nil;
MTRDeviceResubscriptionScheduledHandler _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
4 changes: 2 additions & 2 deletions src/darwin/Framework/CHIP/MTRDevice.mm
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ - (id)strongObject
public:
SubscriptionCallback(dispatch_queue_t queue, DataReportCallback attributeReportCallback, DataReportCallback eventReportCallback,
ErrorCallback errorCallback, SubscriptionEstablishedHandler subscriptionEstablishedHandler,
ResubscriptionCallback resubscriptionCallback, OnDoneHandler onDoneHandler)
MTRDeviceResubscriptionScheduledHandler resubscriptionCallback, OnDoneHandler onDoneHandler)
: MTRBaseSubscriptionCallback(queue, attributeReportCallback, eventReportCallback, errorCallback, resubscriptionCallback,
subscriptionEstablishedHandler, onDoneHandler)
{
Expand Down Expand Up @@ -331,7 +331,7 @@ - (void)subscribeWithMinInterval:(uint16_t)minInterval maxInterval:(uint16_t)max
// OnSubscriptionEstablished
[self _handleSubscriptionEstablished];
},
^(void) {
^(NSError * error, NSNumber * resubscriptionDelay) {
// OnResubscriptionNeeded
[self _handleResubscriptionNeeded];
},
Expand Down
6 changes: 4 additions & 2 deletions src/darwin/Framework/CHIP/MTRDeviceOverXPC.m
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,11 @@ - (void)subscribeWithQueue:(dispatch_queue_t)queue
attributeReportHandler:(nullable void (^)(NSArray * value))attributeReportHandler
eventReportHandler:(nullable void (^)(NSArray * value))eventReportHandler
errorHandler:(void (^)(NSError * error))errorHandler
subscriptionEstablished:(nullable void (^)(void))subscriptionEstablishedHandler;
subscriptionEstablished:(nullable void (^)(void))subscriptionEstablishedHandler
resubscriptionScheduled:(MTRDeviceResubscriptionScheduledHandler _Nullable)resubscriptionScheduledHandler
{
MTR_LOG_DEBUG("Subscribing all attributes... Note that reportHandler is not supported.");
MTR_LOG_DEBUG("Subscribing all attributes... Note that attributeReportHandler, eventReportHandler, and resubscriptionScheduled "
"are not supported.");
if (attributeCacheContainer) {
[attributeCacheContainer setXPCConnection:_xpcConnection controllerId:self.controller deviceId:self.nodeId];
}
Expand Down
3 changes: 2 additions & 1 deletion src/darwin/Framework/CHIPTests/MTRDeviceTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -776,7 +776,8 @@ - (void)test011_ReadCachedAttribute
}
subscriptionEstablished:^() {
[subscribeExpectation fulfill];
}];
}
resubscriptionScheduled:nil];
[self waitForExpectations:@[ subscribeExpectation ] timeout:60];

// Invoke command to set the attribute to a known state
Expand Down
Loading

0 comments on commit 65f28f0

Please sign in to comment.