From 23a2c555c0de45b8a8ce055b207aa8ded5ece8ab Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Mon, 26 Aug 2024 20:37:09 -0400 Subject: [PATCH] Remove handling of the unitTestInject* bits in base MTRDevice. (#35180) These injections need to be handled by subclasses, because the subclasses do very different report processing and have different mechanisms for getting reports. Once these implementations are removed, the following become unreachable and can be removed: * _handleEventReport * _handleReportBegin * _handleAttributeReport * _handleReportEnd Once those are removed, the following become unreachable or unused and can be removed: * _receivingReport (always false) * _receivingPrimingReport (always false, because _receivingReport is always false) * _changeState * _callDelegateDeviceCachePrimed * unreportedEvents * _getAttributesToReportWithReportedValues Once those are removed, the following become unreachable or unused and can be removed: * _setCachedAttributeValue * _noteDataVersion * _deviceConfigurationChanged * estimatedStartTimeFromGeneralDiagnosticsUpTime * _attributeAffectsDeviceConfiguration * _pruneStoredDataForPath Once those are removed, the following become unreachable or unused and can be removed: * _pruneEndpointsIn * _pruneClustersIn * _pruneAttributesIn * _attributePathAffectsDescriptionData * _addInformationalAttributesToCurrentMetricScope * AttributeHasChangesOmittedQuality Once those are removed, the following become unreachable or unused and can be removed: * _removeAttributes * _removeClusters Once those are removed, the following become unreachable or unused and can be removed: * _removeCachedAttribute --- src/darwin/Framework/CHIP/MTRDevice.mm | 742 +------------------------ 1 file changed, 3 insertions(+), 739 deletions(-) diff --git a/src/darwin/Framework/CHIP/MTRDevice.mm b/src/darwin/Framework/CHIP/MTRDevice.mm index f913f29388f5fb..134042f974dd54 100644 --- a/src/darwin/Framework/CHIP/MTRDevice.mm +++ b/src/darwin/Framework/CHIP/MTRDevice.mm @@ -260,9 +260,6 @@ @interface MTRDevice () @property (nonatomic, readonly) os_unfair_lock timeSyncLock; @property (nonatomic) chip::FabricIndex fabricIndex; -@property (nonatomic) NSMutableArray *> * 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): // Actively receiving report @@ -287,8 +284,6 @@ @interface MTRDevice () @property (nonatomic) BOOL timeUpdateScheduled; -@property (nonatomic) NSDate * estimatedStartTimeFromGeneralDiagnosticsUpTime; - @property (nonatomic) NSMutableDictionary * temporaryMetaDataCache; @end @@ -327,10 +322,6 @@ @implementation MTRDevice { // right now (because they have been evicted). NSMutableSet * _persistedClusters; - // This boolean keeps track of any device configuration changes received in an attribute report. - // If this is true when the report ends, we notify the delegate. - BOOL _deviceConfigurationChanged; - // Storage behavior configuration and variables to keep track of the logic // _clusterDataPersistenceFirstScheduledTime is used to track the start time of the delay between // report and persistence. @@ -371,6 +362,7 @@ - (instancetype)initForSubclassesWithNodeID:(NSNumber *)nodeID controller:(MTRDe _delegates = [NSMutableSet set]; _deviceController = controller; _nodeID = nodeID; + _state = MTRDeviceStateUnknown; } return self; @@ -820,41 +812,6 @@ - (void)_callFirstDelegateSynchronouslyWithBlock:(void (^)(id } #endif -- (void)_callDelegateDeviceCachePrimed -{ - os_unfair_lock_assert_owner(&self->_lock); - [self _callDelegatesWithBlock:^(id delegate) { - if ([delegate respondsToSelector:@selector(deviceCachePrimed:)]) { - [delegate deviceCachePrimed:self]; - } - }]; -} - -// assume lock is held -- (void)_changeState:(MTRDeviceState)state -{ - os_unfair_lock_assert_owner(&self->_lock); - MTRDeviceState lastState = _state; - _state = state; - if (lastState != state) { - if (state != MTRDeviceStateReachable) { - MTR_LOG("%@ reachability state change %lu => %lu, set estimated start time to nil", self, static_cast(lastState), - static_cast(state)); - _estimatedStartTime = nil; - _estimatedStartTimeFromGeneralDiagnosticsUpTime = nil; - } else { - MTR_LOG( - "%@ reachability state change %lu => %lu", self, static_cast(lastState), static_cast(state)); - } - [self _callDelegatesWithBlock:^(id delegate) { - [delegate device:self stateChanged:state]; - }]; - } else { - MTR_LOG( - "%@ Not reporting reachability state change, since no change in state %lu => %lu", self, static_cast(lastState), static_cast(state)); - } -} - #ifdef DEBUG - (MTRInternalDeviceState)_getInternalState { @@ -906,20 +863,6 @@ - (BOOL)_deviceUsesThread return (networkCommissioningClusterFeatureMapValue & MTRNetworkCommissioningFeatureThreadNetworkInterface) != 0 ? YES : NO; } -- (void)_handleReportBegin -{ - std::lock_guard lock(_lock); - - _receivingReport = YES; - if (_state != MTRDeviceStateReachable) { - [self _changeState:MTRDeviceStateReachable]; - } - - // If we currently don't have an established subscription, this must be a - // priming report. - _receivingPrimingReport = YES; -} - - (NSDictionary *)_clusterDataToPersistSnapshot { os_unfair_lock_assert_owner(&self->_lock); @@ -1202,49 +1145,6 @@ - (void)setStorageBehaviorConfiguration:(MTRDeviceStorageBehaviorConfiguration * [self _resetStorageBehaviorState]; } -- (void)_handleReportEnd -{ - std::lock_guard lock(_lock); - _receivingReport = NO; - _receivingPrimingReport = NO; - _estimatedStartTimeFromGeneralDiagnosticsUpTime = nil; - - [self _scheduleClusterDataPersistence]; - - // After the handling of the report, if we detected a device configuration change, notify the delegate - // of the same. - if (_deviceConfigurationChanged) { - [self _callDelegatesWithBlock:^(id delegate) { - if ([delegate respondsToSelector:@selector(deviceConfigurationChanged:)]) { - [delegate deviceConfigurationChanged:self]; - } - }]; - _deviceConfigurationChanged = NO; - } - - // Do this after the _deviceConfigurationChanged check, so that we don't - // call deviceConfigurationChanged: immediately after telling our delegate - // we are now primed. - // - // TODO: Maybe we shouldn't dispatch deviceConfigurationChanged: for the - // initial priming bits? - if (!_deviceCachePrimed) { - // This is the end of the priming sequence of data reports, so we have - // all the data for the device now. - _deviceCachePrimed = YES; - [self _callDelegateDeviceCachePrimed]; - } - -// For unit testing only -#ifdef DEBUG - [self _callDelegatesWithBlock:^(id testDelegate) { - if ([testDelegate respondsToSelector:@selector(unitTestReportEndForDevice:)]) { - [testDelegate unitTestReportEndForDevice:self]; - } - }]; -#endif -} - - (BOOL)_interestedPaths:(NSArray * _Nullable)interestedPaths includesAttributePath:(MTRAttributePath *)attributePath { for (id interestedPath in interestedPaths) { @@ -1316,29 +1216,15 @@ - (void)_reportAttributes:(NSArray *> *)attributes } } -- (void)_handleAttributeReport:(NSArray *> *)attributeReport fromSubscription:(BOOL)isFromSubscription -{ - std::lock_guard lock(_lock); - - // _getAttributesToReportWithReportedValues will log attribute paths reported - [self _reportAttributes:[self _getAttributesToReportWithReportedValues:attributeReport fromSubscription:isFromSubscription]]; -} - #ifdef DEBUG - (void)unitTestInjectEventReport:(NSArray *> *)eventReport { - dispatch_async(self.queue, ^{ - [self _handleEventReport:eventReport]; - }); + NSAssert(NO, @"Unit test injection of reports needs to be handled by subclasses"); } - (void)unitTestInjectAttributeReport:(NSArray *> *)attributeReport fromSubscription:(BOOL)isFromSubscription { - dispatch_async(self.queue, ^{ - [self _handleReportBegin]; - [self _handleAttributeReport:attributeReport fromSubscription:isFromSubscription]; - [self _handleReportEnd]; - }); + NSAssert(NO, @"Unit test injection of reports needs to be handled by subclasses"); } #endif @@ -1396,111 +1282,6 @@ - (BOOL)_interestedPaths:(NSArray * _Nullable)interestedPaths includesEventPath: return filteredEvents; } -- (void)_handleEventReport:(NSArray *> *)eventReport -{ - std::lock_guard lock(_lock); - - NSDate * oldEstimatedStartTime = _estimatedStartTime; - // Combine with previous unreported events, if they exist - NSMutableArray * reportToReturn; - if (_unreportedEvents) { - reportToReturn = _unreportedEvents; - } else { - reportToReturn = [NSMutableArray array]; - } - for (NSDictionary * eventDict in eventReport) { - // Whenever a StartUp event is received, reset the estimated start time - // New subscription case - // - Starts Unreachable - // - Start CASE and send subscription request - // - Receive priming report ReportBegin - // - Optionally receive UpTime attribute - update time and save start time estimate - // - Optionally receive StartUp event - // - Set estimated system time from event receipt time, or saved UpTime estimate if exists - // - ReportEnd handler clears the saved start time estimate based on UpTime - // Subscription dropped from client point of view case - // - Starts Unreachable - // - Resubscribe happens after some time, and then same as the above - // Server resuming subscription after reboot case - // - Starts Reachable - // - Receive priming report ReportBegin - // - Optionally receive UpTime attribute - update time and save value - // - Optionally receive StartUp event - // - Set estimated system time from event receipt time, or saved UpTime estimate if exists - // - ReportEnd handler clears the saved start time estimate based on UpTime - // Server resuming subscription after timeout case - // - Starts Reachable - // - Receive priming report ReportBegin - // - Optionally receive UpTime attribute - update time and save value - // - ReportEnd handler clears the saved start time estimate based on UpTime - MTREventPath * eventPath = eventDict[MTREventPathKey]; - BOOL isStartUpEvent = (eventPath.cluster.unsignedLongValue == MTRClusterIDTypeBasicInformationID) - && (eventPath.event.unsignedLongValue == MTREventIDTypeClusterBasicInformationEventStartUpID); - if (isStartUpEvent) { - if (_estimatedStartTimeFromGeneralDiagnosticsUpTime) { - // If UpTime was received, make use of it as mark of system start time - MTR_LOG("%@ StartUp event: set estimated start time forward to %@", self, - _estimatedStartTimeFromGeneralDiagnosticsUpTime); - _estimatedStartTime = _estimatedStartTimeFromGeneralDiagnosticsUpTime; - } else { - // If UpTime was not received, reset estimated start time in case of reboot - MTR_LOG("%@ StartUp event: set estimated start time to nil", self); - _estimatedStartTime = nil; - } - } - - // If event time is of MTREventTimeTypeSystemUpTime type, then update estimated start time as needed - NSNumber * eventTimeTypeNumber = eventDict[MTREventTimeTypeKey]; - if (!eventTimeTypeNumber) { - MTR_LOG_ERROR("%@ Event %@ missing event time type", self, eventDict); - continue; - } - MTREventTimeType eventTimeType = (MTREventTimeType) eventTimeTypeNumber.unsignedIntegerValue; - if (eventTimeType == MTREventTimeTypeSystemUpTime) { - NSNumber * eventTimeValueNumber = eventDict[MTREventSystemUpTimeKey]; - if (!eventTimeValueNumber) { - MTR_LOG_ERROR("%@ Event %@ missing event time value", self, eventDict); - continue; - } - NSTimeInterval eventTimeValue = eventTimeValueNumber.doubleValue; - NSDate * potentialSystemStartTime = [NSDate dateWithTimeIntervalSinceNow:-eventTimeValue]; - if (!_estimatedStartTime || ([potentialSystemStartTime compare:_estimatedStartTime] == NSOrderedAscending)) { - _estimatedStartTime = potentialSystemStartTime; - } - } - - NSMutableDictionary * eventToReturn = eventDict.mutableCopy; - if (_receivingPrimingReport) { - eventToReturn[MTREventIsHistoricalKey] = @(YES); - } else { - eventToReturn[MTREventIsHistoricalKey] = @(NO); - } - - [reportToReturn addObject:eventToReturn]; - } - if (oldEstimatedStartTime != _estimatedStartTime) { - MTR_LOG("%@ updated estimated start time to %@", self, _estimatedStartTime); - } - - __block BOOL delegatesCalled = NO; - [self _iterateDelegatesWithBlock:^(MTRDeviceDelegateInfo * delegateInfo) { - // _iterateDelegatesWithBlock calls this with an autorelease pool, and so temporary filtered event reports don't bloat memory - NSArray *> * filteredEvents = [self _filteredEvents:reportToReturn forInterestedPaths:delegateInfo.interestedPathsForEvents]; - if (filteredEvents.count) { - [delegateInfo callDelegateWithBlock:^(id delegate) { - [delegate device:self receivedEventReport:filteredEvents]; - }]; - delegatesCalled = YES; - } - }]; - if (delegatesCalled) { - _unreportedEvents = nil; - } else { - // save unreported events - _unreportedEvents = reportToReturn; - } -} - #ifdef DEBUG - (void)unitTestClearClusterData { @@ -1614,63 +1395,6 @@ - (MTRDeviceDataValueDictionary _Nullable)_cachedAttributeValueForPath:(MTRAttri return clusterData.attributes[path.attribute]; } -- (void)_setCachedAttributeValue:(MTRDeviceDataValueDictionary _Nullable)value forPath:(MTRAttributePath *)path fromSubscription:(BOOL)isFromSubscription -{ - os_unfair_lock_assert_owner(&self->_lock); - - // We need an actual MTRClusterPath, not a subclass, to do _clusterDataForPath. - auto * clusterPath = [MTRClusterPath clusterPathWithEndpointID:path.endpoint clusterID:path.cluster]; - - MTRDeviceClusterData * clusterData = [self _clusterDataForPath:clusterPath]; - if (clusterData == nil) { - if (value == nil) { - // Nothing to do. - return; - } - - clusterData = [[MTRDeviceClusterData alloc] init]; - } - - [clusterData storeValue:value forAttribute:path.attribute]; - - if ([self _attributePathAffectsDescriptionData:path]) { - [self _updateAttributeDependentDescriptionData]; - } - - if (value != nil - && isFromSubscription - && !_receivingPrimingReport - && AttributeHasChangesOmittedQuality(path)) { - // Do not persist new values for Changes Omitted Quality (aka C Quality) - // attributes unless they're part of a Priming Report or from a read response. - // (removals are OK) - - // log when a device violates expectations for Changes Omitted Quality attributes. - using namespace chip::Tracing::DarwinFramework; - MATTER_LOG_METRIC_BEGIN(kMetricUnexpectedCQualityUpdate); - [self _addInformationalAttributesToCurrentMetricScope]; - MATTER_LOG_METRIC_END(kMetricUnexpectedCQualityUpdate); - - return; - } - - if (_clusterDataToPersist == nil) { - _clusterDataToPersist = [NSMutableDictionary dictionary]; - } - _clusterDataToPersist[clusterPath] = clusterData; -} - -- (void)_removeCachedAttribute:(NSNumber *)attributeID fromCluster:(MTRClusterPath *)clusterPath -{ - os_unfair_lock_assert_owner(&self->_lock); - - if (_clusterDataToPersist == nil) { - return; - } - auto * clusterData = _clusterDataToPersist[clusterPath]; - [clusterData removeValueForAttribute:attributeID]; -} - #ifdef DEBUG - (void)unitTestResetSubscription { @@ -1700,132 +1424,6 @@ - (NSUInteger)unitTestNonnullDelegateCount #pragma mark Device Interactions -// Helper function to determine whether an attribute has "Changes Omitted" quality, which indicates that past the priming report in -// a subscription, this attribute is not expected to be reported when its value changes -// * TODO: xml+codegen version to replace this hardcoded list. -static BOOL AttributeHasChangesOmittedQuality(MTRAttributePath * attributePath) -{ - switch (attributePath.cluster.unsignedLongValue) { - case MTRClusterEthernetNetworkDiagnosticsID: - switch (attributePath.attribute.unsignedLongValue) { - case MTRClusterEthernetNetworkDiagnosticsAttributePacketRxCountID: - case MTRClusterEthernetNetworkDiagnosticsAttributePacketTxCountID: - case MTRClusterEthernetNetworkDiagnosticsAttributeTxErrCountID: - case MTRClusterEthernetNetworkDiagnosticsAttributeCollisionCountID: - case MTRClusterEthernetNetworkDiagnosticsAttributeOverrunCountID: - case MTRClusterEthernetNetworkDiagnosticsAttributeCarrierDetectID: - case MTRClusterEthernetNetworkDiagnosticsAttributeTimeSinceResetID: - return YES; - default: - return NO; - } - case MTRClusterGeneralDiagnosticsID: - switch (attributePath.attribute.unsignedLongValue) { - case MTRClusterGeneralDiagnosticsAttributeUpTimeID: - case MTRClusterGeneralDiagnosticsAttributeTotalOperationalHoursID: - return YES; - default: - return NO; - } - case MTRClusterThreadNetworkDiagnosticsID: - switch (attributePath.attribute.unsignedLongValue) { - case MTRClusterThreadNetworkDiagnosticsAttributeOverrunCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeDetachedRoleCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeChildRoleCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeRouterRoleCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeLeaderRoleCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeAttachAttemptCountID: - case MTRClusterThreadNetworkDiagnosticsAttributePartitionIdChangeCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeBetterPartitionAttachAttemptCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeParentChangeCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeTxTotalCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeTxUnicastCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeTxBroadcastCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeTxAckRequestedCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeTxAckedCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeTxNoAckRequestedCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeTxDataCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeTxDataPollCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeTxBeaconCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeTxBeaconRequestCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeTxOtherCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeTxRetryCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeTxDirectMaxRetryExpiryCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeTxIndirectMaxRetryExpiryCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeTxErrCcaCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeTxErrAbortCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeTxErrBusyChannelCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeRxTotalCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeRxUnicastCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeRxBroadcastCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeRxDataCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeRxDataPollCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeRxBeaconCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeRxBeaconRequestCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeRxOtherCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeRxAddressFilteredCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeRxDestAddrFilteredCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeRxDuplicatedCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeRxErrNoFrameCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeRxErrUnknownNeighborCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeRxErrInvalidSrcAddrCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeRxErrSecCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeRxErrFcsCountID: - case MTRClusterThreadNetworkDiagnosticsAttributeRxErrOtherCountID: - return YES; - default: - return NO; - } - case MTRClusterWiFiNetworkDiagnosticsID: - switch (attributePath.attribute.unsignedLongValue) { - case MTRClusterWiFiNetworkDiagnosticsAttributeRssiID: - case MTRClusterWiFiNetworkDiagnosticsAttributeBeaconLostCountID: - case MTRClusterWiFiNetworkDiagnosticsAttributeBeaconRxCountID: - case MTRClusterWiFiNetworkDiagnosticsAttributePacketMulticastRxCountID: - case MTRClusterWiFiNetworkDiagnosticsAttributePacketMulticastTxCountID: - case MTRClusterWiFiNetworkDiagnosticsAttributePacketUnicastRxCountID: - case MTRClusterWiFiNetworkDiagnosticsAttributePacketUnicastTxCountID: - case MTRClusterWiFiNetworkDiagnosticsAttributeCurrentMaxRateID: - case MTRClusterWiFiNetworkDiagnosticsAttributeOverrunCountID: - return YES; - default: - return NO; - } - case MTRClusterOperationalCredentialsID: - switch (attributePath.attribute.unsignedLongValue) { - case MTRClusterOperationalCredentialsAttributeNOCsID: - case MTRClusterOperationalCredentialsAttributeTrustedRootCertificatesID: - return YES; - default: - return NO; - } - case MTRClusterPowerSourceID: - switch (attributePath.attribute.unsignedLongValue) { - case MTRClusterPowerSourceAttributeWiredAssessedInputVoltageID: - case MTRClusterPowerSourceAttributeWiredAssessedInputFrequencyID: - case MTRClusterPowerSourceAttributeWiredAssessedCurrentID: - case MTRClusterPowerSourceAttributeBatVoltageID: - case MTRClusterPowerSourceAttributeBatPercentRemainingID: - case MTRClusterPowerSourceAttributeBatTimeRemainingID: - case MTRClusterPowerSourceAttributeBatTimeToFullChargeID: - case MTRClusterPowerSourceAttributeBatChargingCurrentID: - return YES; - default: - return NO; - } - case MTRClusterTimeSynchronizationID: - switch (attributePath.attribute.unsignedLongValue) { - case MTRClusterTimeSynchronizationAttributeUTCTimeID: - case MTRClusterTimeSynchronizationAttributeLocalTimeID: - return YES; - default: - return NO; - } - default: - return NO; - } -} - - (NSDictionary * _Nullable)readAttributeWithEndpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID attributeID:(NSNumber *)attributeID @@ -2196,304 +1794,6 @@ - (NSDictionary *)_dataValueWithoutDataVersion:(NSDictionary *)attributeValue } } -// Update cluster data version and also note the change, so at onReportEnd it can be persisted -- (void)_noteDataVersion:(NSNumber *)dataVersion forClusterPath:(MTRClusterPath *)clusterPath -{ - os_unfair_lock_assert_owner(&self->_lock); - - BOOL dataVersionChanged = NO; - // Update data version used for subscription filtering - MTRDeviceClusterData * clusterData = [self _clusterDataForPath:clusterPath]; - if (!clusterData) { - clusterData = [[MTRDeviceClusterData alloc] initWithDataVersion:dataVersion attributes:nil]; - dataVersionChanged = YES; - } else if (![clusterData.dataVersion isEqualToNumber:dataVersion]) { - clusterData.dataVersion = dataVersion; - dataVersionChanged = YES; - } - - if (dataVersionChanged) { - if (_clusterDataToPersist == nil) { - _clusterDataToPersist = [NSMutableDictionary dictionary]; - } - _clusterDataToPersist[clusterPath] = clusterData; - } -} - -- (BOOL)_attributeAffectsDeviceConfiguration:(MTRAttributePath *)attributePath -{ - // Check for attributes in the descriptor cluster that affect device configuration. - if (attributePath.cluster.unsignedLongValue == MTRClusterIDTypeDescriptorID) { - switch (attributePath.attribute.unsignedLongValue) { - case MTRAttributeIDTypeClusterDescriptorAttributePartsListID: - case MTRAttributeIDTypeClusterDescriptorAttributeServerListID: - case MTRAttributeIDTypeClusterDescriptorAttributeDeviceTypeListID: { - return YES; - } - } - } - - // Check for global attributes that affect device configuration. - switch (attributePath.attribute.unsignedLongValue) { - case MTRAttributeIDTypeGlobalAttributeAcceptedCommandListID: - case MTRAttributeIDTypeGlobalAttributeAttributeListID: - case MTRAttributeIDTypeGlobalAttributeClusterRevisionID: - case MTRAttributeIDTypeGlobalAttributeFeatureMapID: - return YES; - } - return NO; -} - -- (void)_removeClusters:(NSSet *)clusterPathsToRemove - doRemoveFromDataStore:(BOOL)doRemoveFromDataStore -{ - os_unfair_lock_assert_owner(&self->_lock); - - [_persistedClusters minusSet:clusterPathsToRemove]; - - for (MTRClusterPath * path in clusterPathsToRemove) { - [_persistedClusterData removeObjectForKey:path]; - [_clusterDataToPersist removeObjectForKey:path]; - if (doRemoveFromDataStore) { - [self.deviceController.controllerDataStore clearStoredClusterDataForNodeID:self.nodeID endpointID:path.endpoint clusterID:path.cluster]; - } - } -} - -- (void)_removeAttributes:(NSSet *)attributes fromCluster:(MTRClusterPath *)clusterPath -{ - os_unfair_lock_assert_owner(&self->_lock); - - for (NSNumber * attribute in attributes) { - [self _removeCachedAttribute:attribute fromCluster:clusterPath]; - } - // Just clear out the NSCache entry for this cluster, so we'll load it from storage as needed. - [_persistedClusterData removeObjectForKey:clusterPath]; - [self.deviceController.controllerDataStore removeAttributes:attributes fromCluster:clusterPath forNodeID:self.nodeID]; -} - -- (void)_pruneEndpointsIn:(MTRDeviceDataValueDictionary)previousPartsListValue - missingFrom:(MTRDeviceDataValueDictionary)newPartsListValue -{ - // If the parts list changed and one or more endpoints were removed, remove all the - // clusters for all those endpoints from our data structures. - // Also remove those endpoints from the data store. - NSMutableSet * toBeRemovedEndpoints = [NSMutableSet setWithArray:[self arrayOfNumbersFromAttributeValue:previousPartsListValue]]; - NSSet * endpointsOnDevice = [NSSet setWithArray:[self arrayOfNumbersFromAttributeValue:newPartsListValue]]; - [toBeRemovedEndpoints minusSet:endpointsOnDevice]; - - for (NSNumber * endpoint in toBeRemovedEndpoints) { - NSMutableSet * clusterPathsToRemove = [[NSMutableSet alloc] init]; - for (MTRClusterPath * path in _persistedClusters) { - if ([path.endpoint isEqualToNumber:endpoint]) { - [clusterPathsToRemove addObject:path]; - } - } - [self _removeClusters:clusterPathsToRemove doRemoveFromDataStore:NO]; - [self.deviceController.controllerDataStore clearStoredClusterDataForNodeID:self.nodeID endpointID:endpoint]; - } -} - -- (void)_pruneClustersIn:(MTRDeviceDataValueDictionary)previousServerListValue - missingFrom:(MTRDeviceDataValueDictionary)newServerListValue - forEndpoint:(NSNumber *)endpointID -{ - // If the server list changed and clusters were removed, remove those clusters from our data structures. - // Also remove them from the data store. - NSMutableSet * toBeRemovedClusters = [NSMutableSet setWithArray:[self arrayOfNumbersFromAttributeValue:previousServerListValue]]; - NSSet * clustersStillOnEndpoint = [NSSet setWithArray:[self arrayOfNumbersFromAttributeValue:newServerListValue]]; - [toBeRemovedClusters minusSet:clustersStillOnEndpoint]; - - NSMutableSet * clusterPathsToRemove = [[NSMutableSet alloc] init]; - for (MTRClusterPath * path in _persistedClusters) { - if ([path.endpoint isEqualToNumber:endpointID] && [toBeRemovedClusters containsObject:path.cluster]) { - [clusterPathsToRemove addObject:path]; - } - } - [self _removeClusters:clusterPathsToRemove doRemoveFromDataStore:YES]; -} - -- (void)_pruneAttributesIn:(MTRDeviceDataValueDictionary)previousAttributeListValue - missingFrom:(MTRDeviceDataValueDictionary)newAttributeListValue - forCluster:(MTRClusterPath *)clusterPath -{ - // If the attribute list changed and attributes were removed, remove the attributes from our - // data structures. - NSMutableSet * toBeRemovedAttributes = [NSMutableSet setWithArray:[self arrayOfNumbersFromAttributeValue:previousAttributeListValue]]; - NSSet * attributesStillInCluster = [NSSet setWithArray:[self arrayOfNumbersFromAttributeValue:newAttributeListValue]]; - - [toBeRemovedAttributes minusSet:attributesStillInCluster]; - [self _removeAttributes:toBeRemovedAttributes fromCluster:clusterPath]; -} - -- (void)_pruneStoredDataForPath:(MTRAttributePath *)attributePath - missingFrom:(MTRDeviceDataValueDictionary)newAttributeDataValue -{ - os_unfair_lock_assert_owner(&self->_lock); - - if (![self _dataStoreExists] && !_clusterDataToPersist.count) { - MTR_LOG_DEBUG("%@ No data store to prune from", self); - return; - } - - // Check if parts list changed or server list changed for the descriptor cluster or the attribute list changed for a cluster. - // If yes, we might need to prune any deleted endpoints, clusters or attributes from the storage and persisted cluster data. - if (attributePath.cluster.unsignedLongValue == MTRClusterIDTypeDescriptorID) { - if (attributePath.attribute.unsignedLongValue == MTRAttributeIDTypeClusterDescriptorAttributePartsListID && [attributePath.endpoint isEqualToNumber:@(kRootEndpointId)]) { - [self _pruneEndpointsIn:[self _cachedAttributeValueForPath:attributePath] missingFrom:newAttributeDataValue]; - return; - } - - if (attributePath.attribute.unsignedLongValue == MTRAttributeIDTypeClusterDescriptorAttributeServerListID) { - [self _pruneClustersIn:[self _cachedAttributeValueForPath:attributePath] missingFrom:newAttributeDataValue forEndpoint:attributePath.endpoint]; - return; - } - } - - if (attributePath.attribute.unsignedLongValue == MTRAttributeIDTypeGlobalAttributeAttributeListID) { - [self _pruneAttributesIn:[self _cachedAttributeValueForPath:attributePath] missingFrom:newAttributeDataValue forCluster:[MTRClusterPath clusterPathWithEndpointID:attributePath.endpoint clusterID:attributePath.cluster]]; - } -} - -// assume lock is held -- (NSArray *)_getAttributesToReportWithReportedValues:(NSArray *> *)reportedAttributeValues fromSubscription:(BOOL)isFromSubscription -{ - os_unfair_lock_assert_owner(&self->_lock); - - NSMutableArray * attributesToReport = [NSMutableArray array]; - NSMutableArray * attributePathsToReport = [NSMutableArray array]; - for (NSDictionary * attributeResponseValue in reportedAttributeValues) { - MTRAttributePath * attributePath = attributeResponseValue[MTRAttributePathKey]; - NSDictionary * attributeDataValue = attributeResponseValue[MTRDataKey]; - NSError * attributeError = attributeResponseValue[MTRErrorKey]; - NSDictionary * previousValue; - - // sanity check either data value or error must exist - if (!attributeDataValue && !attributeError) { - MTR_LOG("%@ report %@ no data value or error: %@", self, attributePath, attributeResponseValue); - 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; - - // if this is an error, report and purge cache - if (attributeError) { - shouldReportAttribute = YES; - previousValue = [self _cachedAttributeValueForPath:attributePath]; - MTR_LOG_ERROR("%@ report %@ error %@ purge expected value %@ read cache %@", self, attributePath, attributeError, - _expectedValueCache[attributePath], previousValue); - _expectedValueCache[attributePath] = nil; - // TODO: Is this clearing business really what we want? - [self _setCachedAttributeValue:nil forPath:attributePath fromSubscription:isFromSubscription]; - } else { - // First separate data version and restore data value to a form without data version - NSNumber * dataVersion = attributeDataValue[MTRDataVersionKey]; - MTRClusterPath * clusterPath = [MTRClusterPath clusterPathWithEndpointID:attributePath.endpoint clusterID:attributePath.cluster]; - if (dataVersion) { - // Remove data version from what we cache in memory - attributeDataValue = [self _dataValueWithoutDataVersion:attributeDataValue]; - } - - previousValue = [self _cachedAttributeValueForPath:attributePath]; -#ifdef DEBUG - __block BOOL readCacheValueChanged = ![self _attributeDataValue:attributeDataValue isEqualToDataValue:previousValue]; -#else - BOOL readCacheValueChanged = ![self _attributeDataValue:attributeDataValue isEqualToDataValue:previousValue]; -#endif - // Now that we have grabbed previousValue, update our cache with the attribute value. - if (readCacheValueChanged) { - if (dataVersion) { - [self _noteDataVersion:dataVersion forClusterPath:clusterPath]; - } - - [self _pruneStoredDataForPath:attributePath missingFrom:attributeDataValue]; - - if (!_deviceConfigurationChanged) { - _deviceConfigurationChanged = [self _attributeAffectsDeviceConfiguration:attributePath]; - if (_deviceConfigurationChanged) { - MTR_LOG("%@ device configuration changed due to changes in attribute %@", self, attributePath); - } - } - - [self _setCachedAttributeValue:attributeDataValue forPath:attributePath fromSubscription:isFromSubscription]; - } - -#ifdef DEBUG - // Unit test only code. - if (!readCacheValueChanged) { - [self _callFirstDelegateSynchronouslyWithBlock:^(id delegate) { - if ([delegate respondsToSelector:@selector(unitTestForceAttributeReportsIfMatchingCache:)]) { - readCacheValueChanged = [delegate unitTestForceAttributeReportsIfMatchingCache:self]; - } - }]; - } -#endif // DEBUG - - NSArray * expectedValue = _expectedValueCache[attributePath]; - - // Report the attribute if a read would get a changed value. This happens - // when our cached value changes and no expected value exists. - if (readCacheValueChanged && !expectedValue) { - shouldReportAttribute = YES; - } - - if (!shouldReportAttribute) { - // If an expected value exists, the attribute will not be reported at this time. - // When the expected value interval expires, the correct value will be reported, - // if needed. - if (expectedValue) { - MTR_LOG("%@ report %@ value filtered - expected value still present", self, attributePath); - } else { - MTR_LOG("%@ report %@ value filtered - same as read cache", self, attributePath); - } - } - - // If General Diagnostics UpTime attribute, update the estimated start time as needed. - if ((attributePath.cluster.unsignedLongValue == MTRClusterGeneralDiagnosticsID) - && (attributePath.attribute.unsignedLongValue == MTRClusterGeneralDiagnosticsAttributeUpTimeID)) { - // verify that the uptime is indeed the data type we want - if ([attributeDataValue[MTRTypeKey] isEqual:MTRUnsignedIntegerValueType]) { - NSNumber * upTimeNumber = attributeDataValue[MTRValueKey]; - NSTimeInterval upTime = upTimeNumber.unsignedLongLongValue; // UpTime unit is defined as seconds in the spec - NSDate * potentialSystemStartTime = [NSDate dateWithTimeIntervalSinceNow:-upTime]; - NSDate * oldSystemStartTime = _estimatedStartTime; - if (!_estimatedStartTime || ([potentialSystemStartTime compare:_estimatedStartTime] == NSOrderedAscending)) { - MTR_LOG("%@ General Diagnostics UpTime %.3lf: estimated start time %@ => %@", self, upTime, - oldSystemStartTime, potentialSystemStartTime); - _estimatedStartTime = potentialSystemStartTime; - } - - // Save estimate in the subscription resumption case, for when StartUp event uses it - _estimatedStartTimeFromGeneralDiagnosticsUpTime = potentialSystemStartTime; - } - } - } - - if (shouldReportAttribute) { - if (previousValue) { - NSMutableDictionary * mutableAttributeResponseValue = attributeResponseValue.mutableCopy; - mutableAttributeResponseValue[MTRPreviousDataKey] = previousValue; - [attributesToReport addObject:mutableAttributeResponseValue]; - } else { - [attributesToReport addObject:attributeResponseValue]; - } - [attributePathsToReport addObject:attributePath]; - } - } - - if (attributePathsToReport.count > 0) { - MTR_LOG("%@ report from reported values %@", self, attributePathsToReport); - } - - return attributesToReport; -} - #ifdef DEBUG - (NSUInteger)unitTestAttributeCount { @@ -2872,44 +2172,8 @@ - (nullable NSNumber *)_informationalProductID return [self _informationalNumberAtAttributePath:productIDPath]; } -- (void)_addInformationalAttributesToCurrentMetricScope -{ - os_unfair_lock_assert_owner(&self->_lock); - - using namespace chip::Tracing::DarwinFramework; - MATTER_LOG_METRIC(kMetricDeviceVendorID, [self _informationalVendorID].unsignedShortValue); - MATTER_LOG_METRIC(kMetricDeviceProductID, [self _informationalProductID].unsignedShortValue); - BOOL usesThread = [self _deviceUsesThread]; - MATTER_LOG_METRIC(kMetricDeviceUsesThread, usesThread); -} - #pragma mark - Description handling -- (BOOL)_attributePathAffectsDescriptionData:(MTRAttributePath *)path -{ - // Technically this does not need to be called while locked, but in - // practice it is, and we want to make sure it's clear that this function - // should never start taking our data lock. - os_unfair_lock_assert_owner(&_lock); - - switch (path.cluster.unsignedLongLongValue) { - case MTRClusterIDTypeBasicInformationID: { - switch (path.attribute.unsignedLongLongValue) { - case MTRAttributeIDTypeClusterBasicInformationAttributeVendorIDID: - case MTRAttributeIDTypeClusterBasicInformationAttributeProductIDID: - return YES; - default: - return NO; - } - } - case MTRClusterIDTypeNetworkCommissioningID: { - return path.attribute.unsignedLongLongValue == MTRAttributeIDTypeGlobalAttributeFeatureMapID; - } - default: - return NO; - } -} - - (void)_updateAttributeDependentDescriptionData { os_unfair_lock_assert_owner(&_lock);