Skip to content

Commit

Permalink
[Darwin] MTRDevice event report should indicate if received during pr…
Browse files Browse the repository at this point in the history
…iming. (#30717)

* [Darwin] MTRDevice event report should include information about whether it's received during priming report, as those are likely to be in the far past or not realtime

* Restyled fix

* Fixed MTR_AVAILABLE macro
  • Loading branch information
jtung-apple authored and pull[bot] committed Feb 20, 2024
1 parent 43a6e38 commit 1315962
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 11 deletions.
4 changes: 4 additions & 0 deletions src/darwin/Framework/CHIP/MTRBaseDevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ NS_ASSUME_NONNULL_BEGIN
* Only present when MTREventTimeTypeKey is MTREventTimeTypeSystemUpTime.
* MTREventTimestampDateKey : NSDate object.
* Only present when MTREventTimeTypeKey is MTREventTimeTypeTimestampDate.
* MTREventIsHistoricalKey : NSNumber-wrapped BOOL value.
* Value is YES if the event is in the far past or not realtime.
* Only present when MTREventPathKey is present.
*
* Only one of MTREventTimestampDateKey and MTREventSystemUpTimeKey will be present, depending on the value for
* MTREventTimeTypeKey.
Expand Down Expand Up @@ -141,6 +144,7 @@ extern NSString * const MTREventPriorityKey MTR_AVAILABLE(ios(16.5), macos(13.4)
extern NSString * const MTREventTimeTypeKey MTR_AVAILABLE(ios(16.5), macos(13.4), watchos(9.5), tvos(16.5));
extern NSString * const MTREventSystemUpTimeKey MTR_AVAILABLE(ios(16.5), macos(13.4), watchos(9.5), tvos(16.5));
extern NSString * const MTREventTimestampDateKey MTR_AVAILABLE(ios(16.5), macos(13.4), watchos(9.5), tvos(16.5));
extern NSString * const MTREventIsHistoricalKey MTR_AVAILABLE(ios(17.3), macos(14.3), watchos(10.3), tvos(17.3));

@class MTRClusterStateCacheContainer;
@class MTRAttributeCacheContainer;
Expand Down
1 change: 1 addition & 0 deletions src/darwin/Framework/CHIP/MTRBaseDevice.mm
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
NSString * const MTREventTimeTypeKey = @"eventTimeType";
NSString * const MTREventSystemUpTimeKey = @"eventSystemUpTime";
NSString * const MTREventTimestampDateKey = @"eventTimestampDate";
NSString * const MTREventIsHistoricalKey = @"eventIsHistorical";

class MTRDataValueDictionaryCallbackBridge;

Expand Down
62 changes: 52 additions & 10 deletions src/darwin/Framework/CHIP/MTRDevice.mm
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,16 @@ @interface MTRDevice ()
@property (nonatomic) chip::FabricIndex fabricIndex;
@property (nonatomic) MTRWeakReference<id<MTRDeviceDelegate>> * weakDelegate;
@property (nonatomic) dispatch_queue_t delegateQueue;
@property (nonatomic) NSArray<NSDictionary<NSString *, id> *> * unreportedEvents;
@property (nonatomic) NSMutableArray<NSDictionary<NSString *, id> *> * unreportedEvents;
@property (nonatomic) BOOL receivingReport;
@property (nonatomic) BOOL receivingPrimingReport;

// TODO: instead of all the BOOL properties that are some facet of the state, move to internal state machine that has (at least):
// Unsubscribed (not attemping)
// Attempting subscription
// Subscribed (gotten subscription response / in steady state with no OnError/OnDone)
// Actively receiving report
// Actively receiving priming report

/**
* If subscriptionActive is true that means that either we are in the middle of
Expand Down Expand Up @@ -453,13 +462,21 @@ - (void)_handleUnsolicitedMessageFromPublisher
- (void)_handleReportBegin
{
os_unfair_lock_lock(&self->_lock);
[self _changeState:MTRDeviceStateReachable];
_receivingReport = YES;
if (_state != MTRDeviceStateReachable) {
_receivingPrimingReport = YES;
[self _changeState:MTRDeviceStateReachable];
} else {
_receivingPrimingReport = NO;
}
os_unfair_lock_unlock(&self->_lock);
}

- (void)_handleReportEnd
{
os_unfair_lock_lock(&self->_lock);
_receivingReport = NO;
_receivingPrimingReport = NO;
_estimatedStartTimeFromGeneralDiagnosticsUpTime = nil;
// For unit testing only
#ifdef DEBUG
Expand Down Expand Up @@ -499,11 +516,27 @@ - (void)_handleAttributeReport:(NSArray<NSDictionary<NSString *, id> *> *)attrib
os_unfair_lock_unlock(&self->_lock);
}

#ifdef DEBUG
- (void)unitTestInjectEventReport:(NSArray<NSDictionary<NSString *, id> *> *)eventReport
{
dispatch_async(self.queue, ^{
[self _handleEventReport:eventReport];
});
}
#endif

- (void)_handleEventReport:(NSArray<NSDictionary<NSString *, id> *> *)eventReport
{
os_unfair_lock_lock(&self->_lock);

NSDate * oldEstimatedStartTime = _estimatedStartTime;
// Combine with previous unreported events, if they exist
NSMutableArray * reportToReturn;
if (_unreportedEvents) {
reportToReturn = _unreportedEvents;
} else {
reportToReturn = [NSMutableArray array];
}
for (NSDictionary<NSString *, id> * eventDict in eventReport) {
// Whenever a StartUp event is received, reset the estimated start time
// New subscription case
Expand Down Expand Up @@ -564,25 +597,29 @@ - (void)_handleEventReport:(NSArray<NSDictionary<NSString *, id> *> *)eventRepor
_estimatedStartTime = potentialSystemStartTime;
}
}

NSMutableDictionary * eventToReturn = eventDict.mutableCopy;
if (_receivingPrimingReport) {
eventToReturn[MTREventIsHistoricalKey] = @(YES);
} else {
eventToReturn[MTREventIsHistoricalKey] = @(NO);
}

[reportToReturn addObject:eventToReturn];
}
if (oldEstimatedStartTime != _estimatedStartTime) {
MTR_LOG_DEFAULT("%@ updated estimated start time to %@", self, _estimatedStartTime);
}

// Combine with previous unreported events, if they exist
if (_unreportedEvents) {
eventReport = [_unreportedEvents arrayByAddingObjectsFromArray:eventReport];
_unreportedEvents = nil;
}

id<MTRDeviceDelegate> delegate = _weakDelegate.strongObject;
if (delegate) {
_unreportedEvents = nil;
dispatch_async(_delegateQueue, ^{
[delegate device:self receivedEventReport:eventReport];
[delegate device:self receivedEventReport:reportToReturn];
});
} else {
// save unreported events
_unreportedEvents = eventReport;
_unreportedEvents = reportToReturn;
}

os_unfair_lock_unlock(&self->_lock);
Expand Down Expand Up @@ -1450,6 +1487,11 @@ - (NSArray *)_getAttributesToReportWithReportedValues:(NSArray<NSDictionary<NSSt
continue;
}

// Additional signal to help mark events as being received during priming report in the event the device rebooted and we get a subscription resumption priming report without noticing it became unreachable first
if (_receivingReport && AttributeHasChangesOmittedQuality(attributePath)) {
_receivingPrimingReport = YES;
}

// check if value is different than cache, and report if needed
BOOL shouldReportAttribute = NO;

Expand Down
29 changes: 28 additions & 1 deletion src/darwin/Framework/CHIPTests/MTRDeviceTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ - (void)failSubscribers:(dispatch_queue_t)queue completion:(void (^)(void))compl
// Test function for whitebox testing
+ (id)CHIPEncodeAndDecodeNSObject:(id)object;
@end

@interface MTRDevice (Test)
- (void)unitTestInjectEventReport:(NSArray<NSDictionary<NSString *, id> *> *)eventReport;
@end
#endif

@interface MTRDeviceTestDeviceControllerDelegate : NSObject <MTRDeviceControllerDelegate>
Expand Down Expand Up @@ -1458,6 +1462,9 @@ - (void)test017_TestMTRDeviceBasics
// can satisfy the test below.
XCTestExpectation * gotReportsExpectation = [self expectationWithDescription:@"Attribute and Event reports have been received"];
__block unsigned eventReportsReceived = 0;
__block BOOL reportEnded = NO;
__block BOOL gotOneNonPrimingEvent = NO;
XCTestExpectation * gotNonPrimingEventExpectation = [self expectationWithDescription:@"Received event outside of priming report"];
delegate.onEventDataReceived = ^(NSArray<NSDictionary<NSString *, id> *> * eventReport) {
eventReportsReceived += eventReport.count;

Expand All @@ -1472,10 +1479,30 @@ - (void)test017_TestMTRDeviceBasics
} else if (eventTimeType == MTREventTimeTypeTimestampDate) {
XCTAssertNotNil(eventDict[MTREventTimestampDateKey]);
}

if (!reportEnded) {
NSNumber * reportIsHistorical = eventDict[MTREventIsHistoricalKey];
XCTAssertTrue(reportIsHistorical.boolValue);
} else {
if (!gotOneNonPrimingEvent) {
NSNumber * reportIsHistorical = eventDict[MTREventIsHistoricalKey];
XCTAssertFalse(reportIsHistorical.boolValue);
gotOneNonPrimingEvent = YES;
[gotNonPrimingEventExpectation fulfill];
}
}
}
};
delegate.onReportEnd = ^() {
reportEnded = YES;
[gotReportsExpectation fulfill];
#ifdef DEBUG
[device unitTestInjectEventReport:@[ @{
MTREventPathKey : [MTREventPath eventPathWithEndpointID:@(1) clusterID:@(1) eventID:@(1)],
MTREventTimeTypeKey : @(MTREventTimeTypeTimestampDate),
MTREventTimestampDateKey : [NSDate date]
} ]];
#endif
};

[device setDelegate:delegate queue:queue];
Expand Down Expand Up @@ -1506,7 +1533,7 @@ - (void)test017_TestMTRDeviceBasics
[device readAttributeWithEndpointID:@(1) clusterID:@(MTRClusterIDTypeLevelControlID) attributeID:@(4) params:nil];
[device readAttributeWithEndpointID:@(1) clusterID:@(MTRClusterIDTypeLevelControlID) attributeID:@(4) params:nil];

[self waitForExpectations:@[ subscriptionExpectation, gotReportsExpectation ] timeout:60];
[self waitForExpectations:@[ subscriptionExpectation, gotReportsExpectation, gotNonPrimingEventExpectation ] timeout:60];

delegate.onReportEnd = nil;

Expand Down

0 comments on commit 1315962

Please sign in to comment.