Skip to content

Commit 4a4cdf7

Browse files
committed
[Darwin] MTRDevice _deviceMayBeReachable should not reset active subscription
1 parent 2bd5aa3 commit 4a4cdf7

File tree

5 files changed

+135
-0
lines changed

5 files changed

+135
-0
lines changed

src/darwin/Framework/CHIP/MTRDevice_Concrete.mm

+35
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) 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

@@ -1697,6 +1701,7 @@ - (void)_handleReportBegin
16971701
assertChipStackLockedByCurrentThread();
16981702

16991703
std::lock_guard lock(_lock);
1704+
self.lastSubscriptionActiveTime = [NSDate now];
17001705

17011706
_receivingReport = YES;
17021707
if (_state != MTRDeviceStateReachable) {
@@ -2669,6 +2674,14 @@ - (void)_resetSubscription
26692674
[self.matterCPPObjectsHolder clearReadClientAndDeleteSubscriptionCallback];
26702675

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

26742687
#ifdef DEBUG
@@ -2774,6 +2787,10 @@ - (void)_setupSubscriptionWithReason:(NSString *)reason
27742787
^(NSArray * value) {
27752788
mtr_strongify(self);
27762789
VerifyOrReturn(self, MTR_LOG_DEBUG("_setupSubscriptionWithReason subscription attribute report called back with nil MTRDevice"));
2790+
{
2791+
std::lock_guard lock(self->_lock);
2792+
self.lastSubscriptionActiveTime = [NSDate now];
2793+
}
27772794

27782795
MTR_LOG("%@ got attribute report (%p) %@", self, value, value);
27792796
dispatch_async(self.queue, ^{
@@ -2790,6 +2807,10 @@ - (void)_setupSubscriptionWithReason:(NSString *)reason
27902807
^(NSArray * value) {
27912808
mtr_strongify(self);
27922809
VerifyOrReturn(self, MTR_LOG_DEBUG("_setupSubscriptionWithReason subscription event report called back with nil MTRDevice"));
2810+
{
2811+
std::lock_guard lock(self->_lock);
2812+
self.lastSubscriptionActiveTime = [NSDate now];
2813+
}
27932814

27942815
MTR_LOG("%@ got event report %@", self, value);
27952816
dispatch_async(self.queue, ^{
@@ -2822,6 +2843,7 @@ - (void)_setupSubscriptionWithReason:(NSString *)reason
28222843

28232844
MTR_LOG("%@ got subscription established", self);
28242845
std::lock_guard lock(self->_lock);
2846+
self.lastSubscriptionActiveTime = [NSDate now];
28252847

28262848
// First synchronously change state
28272849
if (HadSubscriptionEstablishedOnce(self->_internalDeviceState)) {
@@ -4723,8 +4745,21 @@ - (BOOL)_deviceHasActiveSubscription
47234745
return HaveSubscriptionEstablishedRightNow(_internalDeviceState);
47244746
}
47254747

4748+
// TODO: make this configurable - for now use 500ms or 0.5 seconds
4749+
#define MTRDEVICE_ACTIVE_COMMUNICATION_THRESHOLD_SECONDS (0.5)
4750+
47264751
- (void)_deviceMayBeReachable
47274752
{
4753+
// Ignore this call if actively receiving communication from this device
4754+
{
4755+
std::lock_guard lock(self->_lock);
4756+
NSTimeInterval intervalSinceDeviceLastActive = -[self.lastSubscriptionActiveTime timeIntervalSinceNow];
4757+
if (intervalSinceDeviceLastActive < MTRDEVICE_ACTIVE_COMMUNICATION_THRESHOLD_SECONDS) {
4758+
MTR_LOG("%@ _deviceMayBeReachable called and ignored, because last received communication from device %.6lf seconds ago", self, intervalSinceDeviceLastActive);
4759+
return;
4760+
}
4761+
}
4762+
47284763
MTR_LOG("%@ _deviceMayBeReachable called, resetting subscription", self);
47294764
// TODO: This should only be allowed for thread devices
47304765
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)