Skip to content

Commit f1e19ee

Browse files
[Darwin] MTRDevice _deviceMayBeReachable should not reset active subscription (#37545)
* [Darwin] MTRDevice _deviceMayBeReachable should not reset active subscription * Update MTRDevice_Concrete.mm Co-authored-by: Boris Zbarsky <bzbarsky@apple.com> * Addressed review comments --------- Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
1 parent 522876c commit f1e19ee

File tree

5 files changed

+140
-0
lines changed

5 files changed

+140
-0
lines changed

src/darwin/Framework/CHIP/MTRDevice_Concrete.mm

+40
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,9 @@ @interface MTRDevice_Concrete ()
336336
@property (nonatomic) NSDate * lastDeviceBecameActiveCallbackTime;
337337
@property (nonatomic) BOOL throttlingDeviceBecameActiveCallbacks;
338338

339+
// Keep track of the last time we received subscription related communication from the device
340+
@property (nonatomic, nullable) NSDate * lastSubscriptionActiveTime;
341+
339342
/**
340343
* If currentReadClient is non-null, that means that we successfully
341344
* called SendAutoResubscribeRequest on the ReadClient and have not yet gotten
@@ -360,6 +363,7 @@ - (void)unitTestSubscriptionPoolWorkComplete:(MTRDevice *)device;
360363
- (void)unitTestClusterDataPersisted:(MTRDevice *)device;
361364
- (BOOL)unitTestSuppressTimeBasedReachabilityChanges:(MTRDevice *)device;
362365
- (void)unitTestSubscriptionCallbackDeleteForDevice:(MTRDevice *)device;
366+
- (void)unitTestSubscriptionResetForDevice:(MTRDevice *)device;
363367
@end
364368
#endif
365369

@@ -2669,6 +2673,14 @@ - (void)_resetSubscription
26692673
[self.matterCPPObjectsHolder clearReadClientAndDeleteSubscriptionCallback];
26702674

26712675
[self _doHandleSubscriptionError:nil];
2676+
2677+
#ifdef DEBUG
2678+
[self _callFirstDelegateSynchronouslyWithBlock:^(id testDelegate) {
2679+
if ([testDelegate respondsToSelector:@selector(unitTestSubscriptionResetForDevice:)]) {
2680+
[testDelegate unitTestSubscriptionResetForDevice:self];
2681+
}
2682+
}];
2683+
#endif
26722684
}
26732685

26742686
#ifdef DEBUG
@@ -2774,6 +2786,10 @@ - (void)_setupSubscriptionWithReason:(NSString *)reason
27742786
^(NSArray * value) {
27752787
mtr_strongify(self);
27762788
VerifyOrReturn(self, MTR_LOG_DEBUG("_setupSubscriptionWithReason subscription attribute report called back with nil MTRDevice"));
2789+
{
2790+
std::lock_guard lock(self->_lock);
2791+
self.lastSubscriptionActiveTime = [NSDate now];
2792+
}
27772793

27782794
MTR_LOG("%@ got attribute report (%p) %@", self, value, value);
27792795
dispatch_async(self.queue, ^{
@@ -2790,6 +2806,10 @@ - (void)_setupSubscriptionWithReason:(NSString *)reason
27902806
^(NSArray * value) {
27912807
mtr_strongify(self);
27922808
VerifyOrReturn(self, MTR_LOG_DEBUG("_setupSubscriptionWithReason subscription event report called back with nil MTRDevice"));
2809+
{
2810+
std::lock_guard lock(self->_lock);
2811+
self.lastSubscriptionActiveTime = [NSDate now];
2812+
}
27932813

27942814
MTR_LOG("%@ got event report %@", self, value);
27952815
dispatch_async(self.queue, ^{
@@ -2822,6 +2842,7 @@ - (void)_setupSubscriptionWithReason:(NSString *)reason
28222842

28232843
MTR_LOG("%@ got subscription established", self);
28242844
std::lock_guard lock(self->_lock);
2845+
self.lastSubscriptionActiveTime = [NSDate now];
28252846

28262847
// First synchronously change state
28272848
if (HadSubscriptionEstablishedOnce(self->_internalDeviceState)) {
@@ -2866,6 +2887,10 @@ - (void)_setupSubscriptionWithReason:(NSString *)reason
28662887
^(void) {
28672888
mtr_strongify(self);
28682889
VerifyOrReturn(self, MTR_LOG_DEBUG("_setupSubscriptionWithReason subscription report begin called back with nil MTRDevice"));
2890+
{
2891+
std::lock_guard lock(self->_lock);
2892+
self.lastSubscriptionActiveTime = [NSDate now];
2893+
}
28692894

28702895
MTR_LOG("%@ got report begin", self);
28712896
[self _handleReportBegin];
@@ -4723,8 +4748,23 @@ - (BOOL)_deviceHasActiveSubscription
47234748
return HaveSubscriptionEstablishedRightNow(_internalDeviceState);
47244749
}
47254750

4751+
// TODO: make this configurable - for now use 1.5 second
4752+
#define MTRDEVICE_ACTIVE_COMMUNICATION_THRESHOLD_SECONDS (1.5)
4753+
47264754
- (void)_deviceMayBeReachable
47274755
{
4756+
// Ignore this call if actively receiving communication from this device
4757+
{
4758+
std::lock_guard lock(self->_lock);
4759+
if (self.lastSubscriptionActiveTime) {
4760+
NSTimeInterval intervalSinceDeviceLastActive = -[self.lastSubscriptionActiveTime timeIntervalSinceNow];
4761+
if (intervalSinceDeviceLastActive < MTRDEVICE_ACTIVE_COMMUNICATION_THRESHOLD_SECONDS) {
4762+
MTR_LOG("%@ _deviceMayBeReachable called and ignored, because last received communication from device %.6lf seconds ago", self, intervalSinceDeviceLastActive);
4763+
return;
4764+
}
4765+
}
4766+
}
4767+
47284768
MTR_LOG("%@ _deviceMayBeReachable called, resetting subscription", self);
47294769
// TODO: This should only be allowed for thread devices
47304770
mtr_weakify(self);

src/darwin/Framework/CHIPTests/MTRPerControllerStorageTests.m

+90
Original file line numberDiff line numberDiff line change
@@ -3691,4 +3691,94 @@ - (void)testMTRDeviceDealloc
36913691
XCTAssertFalse([controller isRunning]);
36923692
}
36933693

3694+
- (void)testMTRDeviceMaybeUnreachableIgnoredIfReceivingFromDevice
3695+
{
3696+
__auto_type * storageDelegate = [[MTRTestPerControllerStorageWithBulkReadWrite alloc] initWithControllerID:[NSUUID UUID]];
3697+
3698+
__auto_type * factory = [MTRDeviceControllerFactory sharedInstance];
3699+
XCTAssertNotNil(factory);
3700+
3701+
__auto_type queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
3702+
3703+
__auto_type * rootKeys = [[MTRTestKeys alloc] init];
3704+
XCTAssertNotNil(rootKeys);
3705+
3706+
__auto_type * operationalKeys = [[MTRTestKeys alloc] init];
3707+
XCTAssertNotNil(operationalKeys);
3708+
3709+
NSNumber * nodeID = @(333);
3710+
NSNumber * fabricID = @(444);
3711+
3712+
NSError * error;
3713+
3714+
MTRPerControllerStorageTestsCertificateIssuer * certificateIssuer;
3715+
MTRDeviceController * controller = [self startControllerWithRootKeys:rootKeys
3716+
operationalKeys:operationalKeys
3717+
fabricID:fabricID
3718+
nodeID:nodeID
3719+
storage:storageDelegate
3720+
error:&error
3721+
certificateIssuer:&certificateIssuer];
3722+
XCTAssertNil(error);
3723+
XCTAssertNotNil(controller);
3724+
XCTAssertTrue([controller isRunning]);
3725+
3726+
XCTAssertEqualObjects(controller.controllerNodeID, nodeID);
3727+
3728+
// Now commission the device, to test that that works.
3729+
NSNumber * deviceID = @(22);
3730+
certificateIssuer.nextNodeID = deviceID;
3731+
[self commissionWithController:controller newNodeID:deviceID];
3732+
3733+
// We should have established CASE using our operational key.
3734+
XCTAssertEqual(operationalKeys.signatureCount, 1);
3735+
3736+
__auto_type * device = [MTRDevice deviceWithNodeID:deviceID controller:controller];
3737+
__auto_type * delegate = [[MTRDeviceTestDelegate alloc] init];
3738+
3739+
XCTestExpectation * subscriptionReportBegin = [self expectationWithDescription:@"Subscription report begin"];
3740+
XCTestExpectation * subscriptionReportEnd = [self expectationWithDescription:@"Subscription report end"];
3741+
3742+
__weak __auto_type weakDelegate = delegate;
3743+
delegate.onReportBegin = ^{
3744+
[subscriptionReportBegin fulfill];
3745+
__strong __auto_type strongDelegate = weakDelegate;
3746+
strongDelegate.onReportBegin = nil;
3747+
};
3748+
3749+
delegate.onReportEnd = ^{
3750+
[subscriptionReportEnd fulfill];
3751+
__strong __auto_type strongDelegate = weakDelegate;
3752+
strongDelegate.onReportEnd = nil;
3753+
};
3754+
3755+
delegate.onSubscriptionReset = ^{
3756+
XCTFail("Subscription should not be reset from calling _deviceMayBeReachable");
3757+
};
3758+
3759+
[device setDelegate:delegate queue:queue];
3760+
3761+
// First wait for report to begin coming in
3762+
[self waitForExpectations:@[ subscriptionReportBegin ] timeout:60];
3763+
3764+
// Call _deviceMayBeReachable and expect it to be ignored
3765+
[device _deviceMayBeReachable];
3766+
3767+
[self waitForExpectations:@[ subscriptionReportEnd ] timeout:60];
3768+
3769+
[device _deviceMayBeReachable];
3770+
3771+
// Since subscription reset runs on the matter queue, synchronously run a block on the matter queue here to prove the reset did not happen
3772+
[controller syncRunOnWorkQueue:^{
3773+
;
3774+
} error:nil];
3775+
3776+
// Reset our commissionee.
3777+
__auto_type * baseDevice = [MTRBaseDevice deviceWithNodeID:deviceID controller:controller];
3778+
ResetCommissionee(baseDevice, queue, self, kTimeoutInSeconds);
3779+
3780+
[controller shutdown];
3781+
XCTAssertFalse([controller isRunning]);
3782+
}
3783+
36943784
@end

src/darwin/Framework/CHIPTests/TestHelpers/MTRDeviceTestDelegate.h

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ typedef void (^MTRDeviceTestDelegateDataHandler)(NSArray<NSDictionary<NSString *
3737
@property (nonatomic, nullable) dispatch_block_t onSubscriptionPoolWorkComplete;
3838
@property (nonatomic, nullable) dispatch_block_t onClusterDataPersisted;
3939
@property (nonatomic, nullable) dispatch_block_t onSubscriptionCallbackDelete;
40+
@property (nonatomic, nullable) dispatch_block_t onSubscriptionReset;
4041
@end
4142

4243
@interface MTRDeviceTestDelegateWithSubscriptionSetupOverride : MTRDeviceTestDelegate

src/darwin/Framework/CHIPTests/TestHelpers/MTRDeviceTestDelegate.m

+7
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,13 @@ - (void)unitTestSubscriptionCallbackDeleteForDevice:(MTRDevice *)device
126126
}
127127
}
128128

129+
- (void)unitTestSubscriptionResetForDevice:(MTRDevice *)device
130+
{
131+
if (self.onSubscriptionReset != nil) {
132+
self.onSubscriptionReset();
133+
}
134+
}
135+
129136
@end
130137

131138
@implementation MTRDeviceTestDelegateWithSubscriptionSetupOverride

src/darwin/Framework/CHIPTests/TestHelpers/MTRTestDeclarations.h

+2
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,14 @@ NS_ASSUME_NONNULL_BEGIN
5151
@interface MTRDeviceController (Test)
5252
+ (void)forceLocalhostAdvertisingOnly;
5353
- (void)removeDevice:(MTRDevice *)device;
54+
- (void)syncRunOnWorkQueue:(void (^)(void))block error:(NSError * __autoreleasing *)error;
5455
@property (nonatomic, readonly, nullable) id<MTRDeviceControllerDataStoreAttributeStoreMethods> controllerDataStore;
5556
@end
5657

5758
@interface MTRDevice (Test)
5859
- (NSMutableArray<NSNumber *> *)arrayOfNumbersFromAttributeValue:(MTRDeviceDataValueDictionary)dataDictionary;
5960
- (void)setStorageBehaviorConfiguration:(MTRDeviceStorageBehaviorConfiguration *)storageBehaviorConfiguration;
61+
- (void)_deviceMayBeReachable;
6062
@end
6163

6264
#pragma mark - Declarations for items compiled only for DEBUG configuration

0 commit comments

Comments
 (0)