From 2574847dad7c1042e649241c3c39dd48a5d5f5b5 Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Fri, 18 Oct 2024 02:49:27 -0400 Subject: [PATCH] Remove various un-necessary code from MTRDevice. (#36132) * Remove various un-necessary code from MTRDevice. The _deviceInternalStateChanged declaration was duplicated in MTRDevice_Concrete and only needed there. MTRDeviceClusterData implementation can move into its own file. setPersistedClusterData: is only used on MTRDevice_Concrete instances, so its declaration can move to MTRDevice_Concrete and the unused implementation in MTRDevice can be removed. setPersistedDeviceData: is only used on MTRDevice_Concrete instances, so its declaration can move to MTRDevice_Concrete and the unused implementation in MTRDevice can be removed. Now _setLastInitialSubscribeLatency and _updateAttributeDependentDescriptionData on MTRDevice are unused and can be removed. At this point, _informationalVendorID, _informationalProductID, _networkFeatures, _vid, _pid, _allNetworkFeatures, and _descriptionLock are all unused on MTRDevice and can be removed. Now _informationalNumberAtAttributePath is unused on MTRDevice and can be removed. _endpointList is also unused on MTRDevice and can be removed. deviceCachePrimed is implemented by both MTRDevice_XPC and MTRDevice_Concrete, so the implementation on MTRDevice can be removed. unitTestGetClusterDataForPath, unitTestGetPersistedClusters, unitTestClusterHasBeenPersisted, unitTestResetSubscription, unitTestAttributesReportedSinceLastCheck, unitTestClearClusterData, unitTestInjectEventReport, unitTestInjectAttributeReport, and unitTestAttributeCount are only used on MTRDevice_Concrete, so the duplicate implementations on MTRDevice can be removed. At this point, _cachedAttributeValueForPath is unused on MTRDevice and can be removed. Now _getCachedDataVersions is unused on MTRDevice and can be removed. deviceUsesThread is only used by MTRDeviceController_Concrete on MTRDevice_Concrete instances and so can move to MTRDevice_Concrete entirely. Then _deviceUsesThread becomes unused on MTRDevice and can be removed. Then _clusterDataForPath is unused on MTRDevice and can be removed. Now _reconcilePersistedClustersWithStorage is unused on MTRDevice and can be removed. Also, _knownClusters is unused on MTRDevice and can be removed. setStorageBehaviorConfiguration is only used from MTRDeviceController_Concrete (which has an MTRDevice_Concrete) and from unit tests (which have an MTRDevice_Concrete, though they don't know it), so it can be removed from MTRDevice. Then _resetStorageBehaviorState also becomes unused and can be removed. At this point, _scheduleClusterDataPersistence is not used on MTRDevice and can be removed. This makes _persistClusterDataAsNeeded and _deviceCachePrimed unused, and they can also be removed. At this point, _persistClusterData, _deviceIsReportingExcessively, _reportToPersistenceDelayTimeAfterMutiplier, _reportToPersistenceDelayTimeMaxAfterMutiplier, _mostRecentReportTimes, and _clusterDataPersistenceFirstScheduledTime are not used on MTRDevice and can be removed. Now _dataStoreExists, _clusterDataToPersistSnapshot, _storageBehaviorConfiguration, _reportToPersistenceDelayCurrentMultiplier, _deviceReportingExcessivelyStartTime, and _persistedClusters are unused on MTRDevice and can be removed. At this point _clusterDataToPersist and _persistedClusterData are unused on MTRDevice and can be removed. _systemTimeChangeObserverToken was never actually used on MTRDevice (it's only used on MTRDevice_Concrete) and can be removed. The MTRDeviceAttributeReportHandler typedef is unused on MTRDevice and can be removed. kSecondsToWaitBeforeMarkingUnreachableAfterSettingUpSubscription and ENABLE_CONNECTIVITY_MONITORING are unused on MTRDevice and can be removed. assertChipStackLockedByCurrentThread is not used in MTRDevice, so the LockTracker.h include can be removed. MTRAsyncWorkQueue, MTRAttributeIsSpecified, MTRCommandNeedsTimedInvoke, MTRDeviceConnectivityMonitor, MTRDeviceControllerOverXPC, MTRDecodeEventPayload, are all unused in MTRDevice, so the imports of the corresponding headers can be removed. arrayOfNumbersFromAttributeValue is now unused on MTRDevice and can be removed. The fabricIndex property is write-only on both MTRDevice and MTRDevice_Concrete and can be removed on both. _dataValueWithoutDataVersion is unused on MTRDevice and can be removed. * Move MTRDeviceClusterData into a separate header/implementation file. --- src/darwin/Framework/CHIP/MTRDevice.mm | 871 +----------------- .../Framework/CHIP/MTRDeviceClusterData.h | 38 + .../Framework/CHIP/MTRDeviceClusterData.mm | 122 +++ .../CHIP/MTRDeviceControllerDataStore.h | 4 +- .../CHIP/MTRDeviceController_Concrete.mm | 25 +- .../CHIP/MTRDeviceController_Internal.h | 1 + .../CHIP/MTRDeviceDataValueDictionary.h | 23 + .../Framework/CHIP/MTRDevice_Concrete.h | 18 + .../Framework/CHIP/MTRDevice_Concrete.mm | 5 +- .../Framework/CHIP/MTRDevice_Internal.h | 37 - src/darwin/Framework/CHIP/MTRDevice_XPC.mm | 1 + .../Framework/CHIPTests/MTRDeviceTests.m | 1 + .../CHIPTests/MTRPerControllerStorageTests.m | 2 + .../TestHelpers/MTRTestDeclarations.h | 6 +- .../Matter.xcodeproj/project.pbxproj | 12 + 15 files changed, 261 insertions(+), 905 deletions(-) create mode 100644 src/darwin/Framework/CHIP/MTRDeviceClusterData.h create mode 100644 src/darwin/Framework/CHIP/MTRDeviceClusterData.mm create mode 100644 src/darwin/Framework/CHIP/MTRDeviceDataValueDictionary.h diff --git a/src/darwin/Framework/CHIP/MTRDevice.mm b/src/darwin/Framework/CHIP/MTRDevice.mm index a584bd68e06f9f..0bfdf44efb0d9a 100644 --- a/src/darwin/Framework/CHIP/MTRDevice.mm +++ b/src/darwin/Framework/CHIP/MTRDevice.mm @@ -18,21 +18,16 @@ #import #import -#import "MTRAsyncWorkQueue.h" -#import "MTRAttributeSpecifiedCheck.h" #import "MTRBaseClusters.h" #import "MTRBaseDevice_Internal.h" #import "MTRCluster.h" #import "MTRClusterConstants.h" -#import "MTRCommandTimedCheck.h" #import "MTRConversion.h" #import "MTRDefines_Internal.h" -#import "MTRDeviceConnectivityMonitor.h" -#import "MTRDeviceControllerOverXPC.h" #import "MTRDeviceController_Internal.h" +#import "MTRDeviceDataValueDictionary.h" #import "MTRDevice_Internal.h" #import "MTRError_Internal.h" -#import "MTREventTLVValueDecoder_Internal.h" #import "MTRLogging_Internal.h" #import "MTRMetricKeys.h" #import "MTRMetricsCollector.h" @@ -43,18 +38,11 @@ #import "lib/core/CHIPError.h" -#import - -typedef void (^MTRDeviceAttributeReportHandler)(NSArray * _Nonnull); - +// TODO Should these string definitions just move to MTRDevice_Concrete, since +// that's the only place they are used? NSString * const MTRPreviousDataKey = @"previousData"; NSString * const MTRDataVersionKey = @"dataVersion"; -#define kSecondsToWaitBeforeMarkingUnreachableAfterSettingUpSubscription 10 - -// Disabling pending crashes -#define ENABLE_CONNECTIVITY_MONITORING 0 - @implementation MTRDeviceDelegateInfo - (instancetype)initWithDelegate:(id)delegate queue:(dispatch_queue_t)queue interestedPathsForAttributes:(NSArray * _Nullable)interestedPathsForAttributes interestedPathsForEvents:(NSArray * _Nullable)interestedPathsForEvents { @@ -96,127 +84,8 @@ - (BOOL)callDelegateSynchronouslyWithBlock:(void (^)(id))bloc #endif @end -/* BEGIN DRAGONS: Note methods here cannot be renamed, and are used by private callers, do not rename, remove or modify behavior here */ - -@interface NSObject (MatterPrivateForInternalDragonsDoNotFeed) -- (void)_deviceInternalStateChanged:(MTRDevice *)device; -@end - -/* END DRAGONS */ - -using namespace chip; -using namespace chip::app; -using namespace chip::Protocols::InteractionModel; -using namespace chip::Tracing::DarwinFramework; - #pragma mark - MTRDevice -@implementation MTRDeviceClusterData { - NSMutableDictionary * _attributes; -} - -- (void)storeValue:(MTRDeviceDataValueDictionary _Nullable)value forAttribute:(NSNumber *)attribute -{ - _attributes[attribute] = value; -} - -- (void)removeValueForAttribute:(NSNumber *)attribute -{ - [_attributes removeObjectForKey:attribute]; -} - -- (NSDictionary *)attributes -{ - return _attributes; -} - -+ (BOOL)supportsSecureCoding -{ - return YES; -} - -- (NSString *)description -{ - return [NSString stringWithFormat:@"", _dataVersion, static_cast(_attributes.count)]; -} - -- (nullable instancetype)init -{ - return [self initWithDataVersion:nil attributes:nil]; -} - -// Attributes dictionary is: attributeID => data-value dictionary -- (nullable instancetype)initWithDataVersion:(NSNumber * _Nullable)dataVersion attributes:(NSDictionary * _Nullable)attributes -{ - self = [super init]; - if (self == nil) { - return nil; - } - - _dataVersion = [dataVersion copy]; - _attributes = [NSMutableDictionary dictionaryWithCapacity:attributes.count]; - [_attributes addEntriesFromDictionary:attributes]; - - return self; -} - -- (nullable instancetype)initWithCoder:(NSCoder *)decoder -{ - self = [super init]; - if (self == nil) { - return nil; - } - - _dataVersion = [decoder decodeObjectOfClass:[NSNumber class] forKey:sDataVersionKey]; - if (_dataVersion != nil && ![_dataVersion isKindOfClass:[NSNumber class]]) { - MTR_LOG_ERROR("MTRDeviceClusterData got %@ for data version, not NSNumber.", _dataVersion); - return nil; - } - - static NSSet * const sAttributeValueClasses = [NSSet setWithObjects:[NSDictionary class], [NSArray class], [NSData class], [NSString class], [NSNumber class], nil]; - _attributes = [decoder decodeObjectOfClasses:sAttributeValueClasses forKey:sAttributesKey]; - if (_attributes != nil && ![_attributes isKindOfClass:[NSDictionary class]]) { - MTR_LOG_ERROR("MTRDeviceClusterData got %@ for attributes, not NSDictionary.", _attributes); - return nil; - } - - return self; -} - -- (void)encodeWithCoder:(NSCoder *)coder -{ - [coder encodeObject:self.dataVersion forKey:sDataVersionKey]; - [coder encodeObject:self.attributes forKey:sAttributesKey]; -} - -- (id)copyWithZone:(NSZone *)zone -{ - return [[MTRDeviceClusterData alloc] initWithDataVersion:_dataVersion attributes:_attributes]; -} - -- (BOOL)isEqualToClusterData:(MTRDeviceClusterData *)otherClusterData -{ - return MTREqualObjects(_dataVersion, otherClusterData.dataVersion) - && MTREqualObjects(_attributes, otherClusterData.attributes); -} - -- (BOOL)isEqual:(id)object -{ - if ([object class] != [self class]) { - return NO; - } - - return [self isEqualToClusterData:object]; -} - -@end - -@interface MTRDevice () - -@property (nonatomic) chip::FabricIndex fabricIndex; - -@end - // Declaring selector so compiler won't complain about testing and calling it in _handleReportEnd #ifdef DEBUG @protocol MTRDeviceUnitTestDelegate @@ -233,54 +102,7 @@ - (BOOL)unitTestSuppressTimeBasedReachabilityChanges:(MTRDevice *)device; @end #endif -@implementation MTRDevice { - // _deviceCachePrimed is true if we have the data that comes from an initial - // subscription priming report (whether it came from storage or from our - // subscription). - BOOL _deviceCachePrimed; - - // _persistedClusterData stores data that we have already persisted (when we have - // cluster data persistence enabled). Nil when we have no persistence enabled. - NSCache * _Nullable _persistedClusterData; - // _clusterDataToPersist stores data that needs to be persisted. If we - // don't have persistence enabled, this is our only data store. Nil if we - // currently have nothing that could need persisting. - NSMutableDictionary * _Nullable _clusterDataToPersist; - // _persistedClusters stores the set of "valid" keys into _persistedClusterData. - // These are keys that could have values in _persistedClusterData even if they don't - // right now (because they have been evicted). - NSMutableSet * _persistedClusters; - - // 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. - // _mostRecentReportTimes is a list of the most recent report timestamps used for calculating - // the running average time between reports. - // _deviceReportingExcessivelyStartTime tracks when a device starts reporting excessively. - // _reportToPersistenceDelayCurrentMultiplier is the current multiplier that is calculated when a - // report comes in. - MTRDeviceStorageBehaviorConfiguration * _storageBehaviorConfiguration; - NSDate * _Nullable _clusterDataPersistenceFirstScheduledTime; - NSMutableArray * _mostRecentReportTimes; - NSDate * _Nullable _deviceReportingExcessivelyStartTime; - double _reportToPersistenceDelayCurrentMultiplier; - - // System time change observer reference - id _systemTimeChangeObserverToken; - - // Protects mutable state used by our description getter. This is a separate lock from "lock" - // so that we don't need to worry about getting our description while holding "lock" (e.g due to - // logging self). This lock _must_ be held narrowly, with no other lock acquisitions allowed - // while it's held, to avoid deadlock. - os_unfair_lock _descriptionLock; - - // State used by our description getter: access to these must be protected by descriptionLock. - NSNumber * _Nullable _vid; // nil if unknown - NSNumber * _Nullable _pid; // nil if unknown - // _allNetworkFeatures is a bitwise or of the feature maps of all network commissioning clusters - // present on the device, or nil if there aren't any. - NSNumber * _Nullable _allNetworkFeatures; -} +@implementation MTRDevice - (instancetype)initForSubclassesWithNodeID:(NSNumber *)nodeID controller:(MTRDeviceController *)controller { @@ -306,8 +128,6 @@ - (instancetype)initWithNodeID:(NSNumber *)nodeID controller:(MTRDeviceControlle - (void)dealloc { - [[NSNotificationCenter defaultCenter] removeObserver:_systemTimeChangeObserverToken]; - // TODO: retain cycle and clean up https://github.com/project-chip/connectedhomeip/issues/34267 MTR_LOG("MTRDevice dealloc: %p", self); } @@ -328,42 +148,6 @@ + (MTRDevice *)deviceWithNodeID:(NSNumber *)nodeID controller:(MTRDeviceControll return [controller deviceForNodeID:nodeID]; } -- (NSMutableArray *)arrayOfNumbersFromAttributeValue:(MTRDeviceDataValueDictionary)dataDictionary -{ - if (![MTRArrayValueType isEqual:dataDictionary[MTRTypeKey]]) { - return nil; - } - - id value = dataDictionary[MTRValueKey]; - if (![value isKindOfClass:NSArray.class]) { - return nil; - } - - NSArray * valueArray = value; - __auto_type outputArray = [NSMutableArray arrayWithCapacity:valueArray.count]; - - for (id item in valueArray) { - if (![item isKindOfClass:NSDictionary.class]) { - return nil; - } - - NSDictionary * itemDictionary = item; - id data = itemDictionary[MTRDataKey]; - if (![data isKindOfClass:NSDictionary.class]) { - return nil; - } - - NSDictionary * dataDictionary = data; - id dataType = dataDictionary[MTRTypeKey]; - id dataValue = dataDictionary[MTRValueKey]; - if (![dataType isKindOfClass:NSString.class] || ![dataValue isKindOfClass:NSNumber.class]) { - return nil; - } - [outputArray addObject:dataValue]; - } - return outputArray; -} - #pragma mark Delegate handling - (void)setDelegate:(id)delegate queue:(dispatch_queue_t)queue @@ -529,449 +313,7 @@ - (void)_callFirstDelegateSynchronouslyWithBlock:(void (^)(id } #endif -- (BOOL)deviceUsesThread -{ - std::lock_guard lock(_lock); - return [self _deviceUsesThread]; -} - -// This method is used for signaling whether to use the subscription pool. This functions as -// a heuristic for whether to throttle subscriptions to the device via a pool of subscriptions. -// If products appear that have both Thread and Wifi enabled but are primarily on wifi, this -// method will need to be updated to reflect that. -- (BOOL)_deviceUsesThread -{ - os_unfair_lock_assert_owner(&self->_lock); - #ifdef DEBUG - // Note: This is a hack to allow our unit tests to test the subscription pooling behavior we have implemented for thread, so we mock devices to be a thread device - __block BOOL pretendThreadEnabled = NO; - [self _callFirstDelegateSynchronouslyWithBlock:^(id testDelegate) { - if ([testDelegate respondsToSelector:@selector(unitTestPretendThreadEnabled:)]) { - pretendThreadEnabled = [testDelegate unitTestPretendThreadEnabled:self]; - } - }]; - if (pretendThreadEnabled) { - return YES; - } -#endif - - MTRClusterPath * networkCommissioningClusterPath = [MTRClusterPath clusterPathWithEndpointID:@(kRootEndpointId) clusterID:@(MTRClusterIDTypeNetworkCommissioningID)]; - MTRDeviceClusterData * networkCommissioningClusterData = [self _clusterDataForPath:networkCommissioningClusterPath]; - NSNumber * networkCommissioningClusterFeatureMapValueNumber = networkCommissioningClusterData.attributes[@(MTRClusterGlobalAttributeFeatureMapID)][MTRValueKey]; - - if (networkCommissioningClusterFeatureMapValueNumber == nil) - return NO; - if (![networkCommissioningClusterFeatureMapValueNumber isKindOfClass:[NSNumber class]]) { - MTR_LOG_ERROR("%@ Unexpected NetworkCommissioning FeatureMap value %@", self, networkCommissioningClusterFeatureMapValueNumber); - return NO; - } - - uint32_t networkCommissioningClusterFeatureMapValue = static_cast(networkCommissioningClusterFeatureMapValueNumber.unsignedLongValue); - - return (networkCommissioningClusterFeatureMapValue & MTRNetworkCommissioningFeatureThreadNetworkInterface) != 0 ? YES : NO; -} - -- (NSDictionary *)_clusterDataToPersistSnapshot -{ - os_unfair_lock_assert_owner(&self->_lock); - NSMutableDictionary * clusterDataToReturn = [NSMutableDictionary dictionary]; - for (MTRClusterPath * clusterPath in _clusterDataToPersist) { - clusterDataToReturn[clusterPath] = [_clusterDataToPersist[clusterPath] copy]; - } - - return clusterDataToReturn; -} - -- (NSTimeInterval)_reportToPersistenceDelayTimeAfterMutiplier -{ - return _storageBehaviorConfiguration.reportToPersistenceDelayTime * _reportToPersistenceDelayCurrentMultiplier; -} - -- (NSTimeInterval)_reportToPersistenceDelayTimeMaxAfterMutiplier -{ - return _storageBehaviorConfiguration.reportToPersistenceDelayTimeMax * _reportToPersistenceDelayCurrentMultiplier; -} - -- (BOOL)_dataStoreExists -{ - os_unfair_lock_assert_owner(&self->_lock); - return _persistedClusterData != nil; -} - -- (void)_persistClusterData -{ - os_unfair_lock_assert_owner(&self->_lock); - - // Sanity check - if (![self _dataStoreExists]) { - MTR_LOG_ERROR("%@ storage behavior: no data store in _persistClusterData!", self); - return; - } - - // Nothing to persist - if (!_clusterDataToPersist.count) { - return; - } - - MTR_LOG("%@ Storing cluster information (data version and attributes) count: %lu", self, static_cast(_clusterDataToPersist.count)); - // We're going to hand out these MTRDeviceClusterData objects to our - // storage implementation, which will try to read them later. Make sure - // we snapshot the state here instead of handing out live copies. - NSDictionary * clusterData = [self _clusterDataToPersistSnapshot]; - [_deviceController.controllerDataStore storeClusterData:clusterData forNodeID:_nodeID]; - for (MTRClusterPath * clusterPath in _clusterDataToPersist) { - [_persistedClusterData setObject:_clusterDataToPersist[clusterPath] forKey:clusterPath]; - [_persistedClusters addObject:clusterPath]; - } - - // TODO: There is one edge case not handled well here: if the - // storeClusterData call above fails somehow, and then the data gets - // evicted from _persistedClusterData, we could end up in a situation - // where when we page things in from storage we have stale values and - // hence effectively lose the delta that we failed to persist. - // - // The only way to handle this would be to detect it when it happens, - // then re-subscribe at that point, which would cause the relevant data - // to be sent to us via the priming read. - _clusterDataToPersist = nil; - -#ifdef DEBUG - [self _callDelegatesWithBlock:^(id testDelegate) { - if ([testDelegate respondsToSelector:@selector(unitTestClusterDataPersisted:)]) { - [testDelegate unitTestClusterDataPersisted:self]; - } - }]; -#endif -} - -- (BOOL)_deviceIsReportingExcessively -{ - os_unfair_lock_assert_owner(&self->_lock); - - if (!_deviceReportingExcessivelyStartTime) { - return NO; - } - - NSTimeInterval intervalSinceDeviceReportingExcessively = -[_deviceReportingExcessivelyStartTime timeIntervalSinceNow]; - BOOL deviceIsReportingExcessively = intervalSinceDeviceReportingExcessively > _storageBehaviorConfiguration.deviceReportingExcessivelyIntervalThreshold; - if (deviceIsReportingExcessively) { - MTR_LOG("%@ storage behavior: device has been reporting excessively for %.3lf seconds", self, intervalSinceDeviceReportingExcessively); - } - return deviceIsReportingExcessively; -} - -- (void)_persistClusterDataAsNeeded -{ - std::lock_guard lock(_lock); - - // Nothing to persist - if (!_clusterDataToPersist.count) { - return; - } - - // This is run with a dispatch_after, and need to check again if this device is reporting excessively - if ([self _deviceIsReportingExcessively]) { - return; - } - - NSDate * lastReportTime = [_mostRecentReportTimes lastObject]; - NSTimeInterval intervalSinceLastReport = -[lastReportTime timeIntervalSinceNow]; - if (intervalSinceLastReport < [self _reportToPersistenceDelayTimeAfterMutiplier]) { - // A report came in after this call was scheduled - - if (!_clusterDataPersistenceFirstScheduledTime) { - MTR_LOG_ERROR("%@ storage behavior: expects _clusterDataPersistenceFirstScheduledTime if _clusterDataToPersist exists", self); - return; - } - - NSTimeInterval intervalSinceFirstScheduledPersistence = -[_clusterDataPersistenceFirstScheduledTime timeIntervalSinceNow]; - if (intervalSinceFirstScheduledPersistence < [self _reportToPersistenceDelayTimeMaxAfterMutiplier]) { - MTR_LOG("%@ storage behavior: not persisting: intervalSinceLastReport %lf intervalSinceFirstScheduledPersistence %lf", self, intervalSinceLastReport, intervalSinceFirstScheduledPersistence); - // The max delay is also not reached - do not persist yet - return; - } - } - - // At this point, there is data to persist, and either _reportToPersistenceDelayTime was - // reached, or _reportToPersistenceDelayTimeMax was reached. Time to persist: - [self _persistClusterData]; - - _clusterDataPersistenceFirstScheduledTime = nil; -} - -- (void)_scheduleClusterDataPersistence -{ - os_unfair_lock_assert_owner(&self->_lock); - - // No persisted data / lack of controller data store - if (![self _dataStoreExists]) { - MTR_LOG_DEBUG("%@ storage behavior: no data store", self); - return; - } - - // Nothing to persist - if (!_clusterDataToPersist.count) { - MTR_LOG_DEBUG("%@ storage behavior: nothing to persist", self); - return; - } - - // If there is no storage behavior configuration, make a default one - if (!_storageBehaviorConfiguration) { - _storageBehaviorConfiguration = [[MTRDeviceStorageBehaviorConfiguration alloc] init]; - [_storageBehaviorConfiguration checkValuesAndResetToDefaultIfNecessary]; - } - - // Directly store if the storage behavior optimization is disabled - if (_storageBehaviorConfiguration.disableStorageBehaviorOptimization) { - [self _persistClusterData]; - return; - } - - // If we have nothing stored at all yet, store directly, so we move into a - // primed state. - if (!_deviceCachePrimed) { - [self _persistClusterData]; - return; - } - - // Ensure there is an array to keep the most recent report times - if (!_mostRecentReportTimes) { - _mostRecentReportTimes = [NSMutableArray array]; - } - - // Mark when first report comes in to know when _reportToPersistenceDelayTimeMax is hit - if (!_clusterDataPersistenceFirstScheduledTime) { - _clusterDataPersistenceFirstScheduledTime = [NSDate now]; - } - - // Make sure there is space in the array, and note report time - while (_mostRecentReportTimes.count >= _storageBehaviorConfiguration.recentReportTimesMaxCount) { - [_mostRecentReportTimes removeObjectAtIndex:0]; - } - [_mostRecentReportTimes addObject:[NSDate now]]; - - // Calculate running average and update multiplier - need at least 2 items to calculate intervals - if (_mostRecentReportTimes.count > 2) { - NSTimeInterval cumulativeIntervals = 0; - for (int i = 1; i < _mostRecentReportTimes.count; i++) { - NSDate * lastDate = [_mostRecentReportTimes objectAtIndex:i - 1]; - NSDate * currentDate = [_mostRecentReportTimes objectAtIndex:i]; - NSTimeInterval intervalSinceLastReport = [currentDate timeIntervalSinceDate:lastDate]; - // Check to guard against clock change - if (intervalSinceLastReport > 0) { - cumulativeIntervals += intervalSinceLastReport; - } - } - NSTimeInterval averageTimeBetweenReports = cumulativeIntervals / (_mostRecentReportTimes.count - 1); - - if (averageTimeBetweenReports < _storageBehaviorConfiguration.timeBetweenReportsTooShortThreshold) { - // Multiplier goes from 1 to _reportToPersistenceDelayMaxMultiplier uniformly, as - // averageTimeBetweenReports go from timeBetweenReportsTooShortThreshold to - // timeBetweenReportsTooShortMinThreshold - - double intervalAmountBelowThreshold = _storageBehaviorConfiguration.timeBetweenReportsTooShortThreshold - averageTimeBetweenReports; - double intervalAmountBetweenThresholdAndMinThreshold = _storageBehaviorConfiguration.timeBetweenReportsTooShortThreshold - _storageBehaviorConfiguration.timeBetweenReportsTooShortMinThreshold; - double proportionTowardMinThreshold = intervalAmountBelowThreshold / intervalAmountBetweenThresholdAndMinThreshold; - if (proportionTowardMinThreshold > 1) { - // Clamp to 100% - proportionTowardMinThreshold = 1; - } - - // Set current multiplier to [1, MaxMultiplier] - _reportToPersistenceDelayCurrentMultiplier = 1 + (proportionTowardMinThreshold * (_storageBehaviorConfiguration.reportToPersistenceDelayMaxMultiplier - 1)); - MTR_LOG("%@ storage behavior: device reporting frequently - setting delay multiplier to %lf", self, _reportToPersistenceDelayCurrentMultiplier); - } else { - _reportToPersistenceDelayCurrentMultiplier = 1; - } - - // Also note when the running average first dips below the min threshold - if (averageTimeBetweenReports < _storageBehaviorConfiguration.timeBetweenReportsTooShortMinThreshold) { - if (!_deviceReportingExcessivelyStartTime) { - _deviceReportingExcessivelyStartTime = [NSDate now]; - MTR_LOG_DEBUG("%@ storage behavior: device is reporting excessively @%@", self, _deviceReportingExcessivelyStartTime); - } - } else { - _deviceReportingExcessivelyStartTime = nil; - } - } - - // Do not schedule persistence if device is reporting excessively - if ([self _deviceIsReportingExcessively]) { - return; - } - - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t) ([self _reportToPersistenceDelayTimeAfterMutiplier] * NSEC_PER_SEC)), self.queue, ^{ - [self _persistClusterDataAsNeeded]; - }); -} - -// Used to clear the storage behavior state when needed (system time change, or when new -// configuration is set. -// -// Also flushes unwritten cluster data to storage, if data store exists. -- (void)_resetStorageBehaviorState -{ - os_unfair_lock_assert_owner(&self->_lock); - - _clusterDataPersistenceFirstScheduledTime = nil; - _mostRecentReportTimes = nil; - _deviceReportingExcessivelyStartTime = nil; - _reportToPersistenceDelayCurrentMultiplier = 1; - - // Sanity check that there is a data - if ([self _dataStoreExists]) { - [self _persistClusterData]; - } -} - -- (void)setStorageBehaviorConfiguration:(MTRDeviceStorageBehaviorConfiguration *)storageBehaviorConfiguration -{ - MTR_LOG("%@ storage behavior: setStorageBehaviorConfiguration %@", self, storageBehaviorConfiguration); - std::lock_guard lock(_lock); - _storageBehaviorConfiguration = storageBehaviorConfiguration; - // Make sure the values are sane - [_storageBehaviorConfiguration checkValuesAndResetToDefaultIfNecessary]; - [self _resetStorageBehaviorState]; -} - -#ifdef DEBUG -- (void)unitTestInjectEventReport:(NSArray *> *)eventReport -{ - NSAssert(NO, @"Unit test injection of reports needs to be handled by subclasses"); -} - -- (void)unitTestInjectAttributeReport:(NSArray *> *)attributeReport fromSubscription:(BOOL)isFromSubscription -{ - NSAssert(NO, @"Unit test injection of reports needs to be handled by subclasses"); -} -#endif - -#ifdef DEBUG -- (void)unitTestClearClusterData -{ - std::lock_guard lock(_lock); - NSAssert([self _dataStoreExists], @"Test is not going to test what it thinks is testing!"); - [_persistedClusterData removeAllObjects]; -} -#endif - -- (void)_reconcilePersistedClustersWithStorage -{ - os_unfair_lock_assert_owner(&self->_lock); - - NSMutableSet * clusterPathsToRemove = [NSMutableSet set]; - for (MTRClusterPath * clusterPath in _persistedClusters) { - MTRDeviceClusterData * data = [_deviceController.controllerDataStore getStoredClusterDataForNodeID:_nodeID endpointID:clusterPath.endpoint clusterID:clusterPath.cluster]; - if (!data) { - [clusterPathsToRemove addObject:clusterPath]; - } - } - - MTR_LOG_ERROR("%@ Storage missing %lu / %lu clusters - reconciling in-memory records", self, static_cast(clusterPathsToRemove.count), static_cast(_persistedClusters.count)); - [_persistedClusters minusSet:clusterPathsToRemove]; -} - -- (nullable MTRDeviceClusterData *)_clusterDataForPath:(MTRClusterPath *)clusterPath -{ - os_unfair_lock_assert_owner(&self->_lock); - - if (_clusterDataToPersist != nil) { - // Use the "dirty" values, if we have them. - MTRDeviceClusterData * data = _clusterDataToPersist[clusterPath]; - if (data != nil) { - return data; - } - } - - if ([self _dataStoreExists]) { - MTRDeviceClusterData * data = [_persistedClusterData objectForKey:clusterPath]; - if (data != nil) { - return data; - } - } - - if (![_persistedClusters containsObject:clusterPath]) { - // We are not expected to have this cluster, so no point in paging it in - // loading it from storage. - return nil; - } - - NSAssert(_deviceController.controllerDataStore != nil, - @"How can _persistedClusters have an entry if we have no persistence?"); - NSAssert(_persistedClusterData != nil, - @"How can _persistedClusterData not exist if we have persisted clusters?"); - - // Page in the stored value for the data. - MTRDeviceClusterData * data = [_deviceController.controllerDataStore getStoredClusterDataForNodeID:_nodeID endpointID:clusterPath.endpoint clusterID:clusterPath.cluster]; - MTR_LOG("%@ cluster path %@ cache miss - load from storage success %@", self, clusterPath, MTR_YES_NO(data)); - if (data != nil) { - [_persistedClusterData setObject:data forKey:clusterPath]; - } else { - // If clusterPath is in _persistedClusters and the data store returns nil for it, then the in-memory cache is now not dependable, and subscription should be reset and reestablished to reload cache from device - - // First make sure _persistedClusters is consistent with storage, so repeated calls don't immediately re-trigger this - [self _reconcilePersistedClustersWithStorage]; - } - - return data; -} - -- (NSSet *)_knownClusters -{ - os_unfair_lock_assert_owner(&self->_lock); - - // We might have some clusters that have not been persisted at all yet, and - // some that have been persisted but are still present in - // _clusterDataToPersist because they have been modified since then. - NSMutableSet * clusterPaths = [_persistedClusters mutableCopy]; - if (_clusterDataToPersist != nil) { - [clusterPaths unionSet:[NSSet setWithArray:[_clusterDataToPersist allKeys]]]; - } - return clusterPaths; -} - -- (NSDictionary *)_getCachedDataVersions -{ - NSMutableDictionary * dataVersions = [NSMutableDictionary dictionary]; - std::lock_guard lock(_lock); - - for (MTRClusterPath * path in [self _knownClusters]) { - dataVersions[path] = [self _clusterDataForPath:path].dataVersion; - } - - MTR_LOG_DEBUG("%@ _getCachedDataVersions dataVersions count: %lu", self, static_cast(dataVersions.count)); - - return dataVersions; -} - -- (MTRDeviceDataValueDictionary _Nullable)_cachedAttributeValueForPath:(MTRAttributePath *)path -{ - os_unfair_lock_assert_owner(&self->_lock); - - // We need an actual MTRClusterPath, not a subsclass, to do _clusterDataForPath. - auto * clusterPath = [MTRClusterPath clusterPathWithEndpointID:path.endpoint clusterID:path.cluster]; - - MTRDeviceClusterData * clusterData = [self _clusterDataForPath:clusterPath]; - if (clusterData == nil) { - return nil; - } - - return clusterData.attributes[path.attribute]; -} - -#ifdef DEBUG -- (void)unitTestResetSubscription -{ -} -#endif - -#ifdef DEBUG -- (NSUInteger)unitTestAttributesReportedSinceLastCheck -{ - return 0; -} - - (NSUInteger)unitTestNonnullDelegateCount { std::lock_guard lock(self->_lock); @@ -1182,215 +524,24 @@ - (void)downloadLogOfType:(MTRDiagnosticLogType)type }); } -#pragma mark - Cache management - -// Utility to return data value dictionary without data version -- (NSDictionary *)_dataValueWithoutDataVersion:(NSDictionary *)attributeValue -{ - // Sanity check for nil - return the same input to fail gracefully - if (!attributeValue || !attributeValue[MTRTypeKey]) { - return attributeValue; - } - - if (attributeValue[MTRValueKey]) { - return @{ MTRTypeKey : attributeValue[MTRTypeKey], MTRValueKey : attributeValue[MTRValueKey] }; - } else { - return @{ MTRTypeKey : attributeValue[MTRTypeKey] }; - } -} - -- (NSArray *> *)getAllAttributesReport +- (nullable NSNumber *)estimatedSubscriptionLatency { MTR_ABSTRACT_METHOD(); return nil; } -#ifdef DEBUG -- (NSUInteger)unitTestAttributeCount -{ - std::lock_guard lock(_lock); - NSUInteger count = 0; - for (MTRClusterPath * path in [self _knownClusters]) { - count += [self _clusterDataForPath:path].attributes.count; - } - return count; -} -#endif - -- (void)setPersistedClusterData:(NSDictionary *)clusterData -{ - MTR_LOG("%@ setPersistedClusterData count: %lu", self, static_cast(clusterData.count)); - if (!clusterData.count) { - return; - } - - std::lock_guard lock(_lock); - - NSAssert([self _dataStoreExists], @"Why is controller setting persisted data when we shouldn't have it?"); - - for (MTRClusterPath * clusterPath in clusterData) { - // The caller has mutable references to MTRDeviceClusterData and - // MTRClusterPath, but that should be OK, since we control all the - // callers. If that stops being OK, we'll need to copy the key and - // value here. - [_persistedClusters addObject:clusterPath]; - [_persistedClusterData setObject:clusterData[clusterPath] forKey:clusterPath]; - } - - [self _updateAttributeDependentDescriptionData]; - - // We have some stored data. Since we don't store data until the end of the - // initial priming report, our device cache must be primed. - _deviceCachePrimed = YES; -} - -- (void)_setLastInitialSubscribeLatency:(id)latency -{ - os_unfair_lock_assert_owner(&self->_lock); - - if (![latency isKindOfClass:NSNumber.class]) { - // Unexpected value of some sort; just ignore it. - return; - } - - _estimatedSubscriptionLatency = latency; -} - -- (void)setPersistedDeviceData:(NSDictionary *)data -{ - MTR_LOG_DEBUG("%@ setPersistedDeviceData: %@", self, data); - - std::lock_guard lock(_lock); - - // For now the only data we care about is our initial subscribe latency. - id initialSubscribeLatency = data[sLastInitialSubscribeLatencyKey]; - if (initialSubscribeLatency != nil) { - [self _setLastInitialSubscribeLatency:initialSubscribeLatency]; - } -} - -#ifdef DEBUG -- (MTRDeviceClusterData *)unitTestGetClusterDataForPath:(MTRClusterPath *)path -{ - std::lock_guard lock(_lock); - - return [[self _clusterDataForPath:path] copy]; -} - -- (NSSet *)unitTestGetPersistedClusters -{ - std::lock_guard lock(_lock); - - return [_persistedClusters copy]; -} +#pragma mark - Cache management -- (BOOL)unitTestClusterHasBeenPersisted:(MTRClusterPath *)path +- (NSArray *> *)getAllAttributesReport { - std::lock_guard lock(_lock); - - return [_persistedClusters containsObject:path]; + MTR_ABSTRACT_METHOD(); + return nil; } -#endif - (BOOL)deviceCachePrimed { - std::lock_guard lock(_lock); - return _deviceCachePrimed; -} - -#pragma mark Log Help - -- (nullable NSNumber *)_informationalNumberAtAttributePath:(MTRAttributePath *)attributePath -{ - auto * cachedData = [self _cachedAttributeValueForPath:attributePath]; - - if (cachedData == nil) { - return nil; - } - - auto * attrReport = [[MTRAttributeReport alloc] initWithResponseValue:@{ - MTRAttributePathKey : attributePath, - MTRDataKey : cachedData, - } - error:nil]; - - return attrReport.value; -} - -- (nullable NSNumber *)_informationalVendorID -{ - auto * vendorIDPath = [MTRAttributePath attributePathWithEndpointID:@(kRootEndpointId) - clusterID:@(MTRClusterIDTypeBasicInformationID) - attributeID:@(MTRClusterBasicAttributeVendorIDID)]; - - return [self _informationalNumberAtAttributePath:vendorIDPath]; -} - -- (nullable NSNumber *)_informationalProductID -{ - auto * productIDPath = [MTRAttributePath attributePathWithEndpointID:@(kRootEndpointId) - clusterID:@(MTRClusterIDTypeBasicInformationID) - attributeID:@(MTRClusterBasicAttributeProductIDID)]; - - return [self _informationalNumberAtAttributePath:productIDPath]; -} - -#pragma mark - Description handling - -- (void)_updateAttributeDependentDescriptionData -{ - os_unfair_lock_assert_owner(&_lock); - - NSNumber * _Nullable vid = [self _informationalVendorID]; - NSNumber * _Nullable pid = [self _informationalProductID]; - NSNumber * _Nullable networkFeatures = [self _networkFeatures]; - - std::lock_guard lock(_descriptionLock); - _vid = vid; - _pid = pid; - _allNetworkFeatures = networkFeatures; -} - -- (NSArray *)_endpointList -{ - os_unfair_lock_assert_owner(&_lock); - - auto * partsListPath = [MTRAttributePath attributePathWithEndpointID:@(kRootEndpointId) - clusterID:@(MTRClusterIDTypeDescriptorID) - attributeID:@(MTRAttributeIDTypeClusterDescriptorAttributePartsListID)]; - auto * partsList = [self _cachedAttributeValueForPath:partsListPath]; - NSMutableArray * endpointsOnDevice = [self arrayOfNumbersFromAttributeValue:partsList]; - if (!endpointsOnDevice) { - endpointsOnDevice = [[NSMutableArray alloc] init]; - } - // Add Root node! - [endpointsOnDevice addObject:@(0)]; - return endpointsOnDevice; -} - -- (NSNumber * _Nullable)_networkFeatures -{ - NSNumber * _Nullable result = nil; - auto * endpoints = [self _endpointList]; - for (NSNumber * endpoint in endpoints) { - auto * featureMapPath = [MTRAttributePath attributePathWithEndpointID:endpoint - clusterID:@(MTRClusterIDTypeNetworkCommissioningID) - attributeID:@(MTRAttributeIDTypeGlobalAttributeFeatureMapID)]; - auto * featureMap = [self _informationalNumberAtAttributePath:featureMapPath]; - if (featureMap == nil) { - // No network commissioning cluster on this endpoint, or no known - // FeatureMap attribute value for it yet. - continue; - } - - if (result == nil) { - result = featureMap; - } else { - result = @(featureMap.unsignedLongLongValue | result.unsignedLongLongValue); - } - } - - return result; + MTR_ABSTRACT_METHOD(); + return NO; } - (void)controllerSuspended diff --git a/src/darwin/Framework/CHIP/MTRDeviceClusterData.h b/src/darwin/Framework/CHIP/MTRDeviceClusterData.h new file mode 100644 index 00000000000000..d34ada90e9a7f2 --- /dev/null +++ b/src/darwin/Framework/CHIP/MTRDeviceClusterData.h @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2024 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "MTRDefines_Internal.h" +#import "MTRDeviceDataValueDictionary.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * Information about a cluster: data version and known attribute values. + */ +MTR_TESTABLE +@interface MTRDeviceClusterData : NSObject +@property (nonatomic, nullable) NSNumber * dataVersion; +@property (nonatomic, readonly) NSDictionary * attributes; // attributeID => data-value dictionary + +- (void)storeValue:(MTRDeviceDataValueDictionary _Nullable)value forAttribute:(NSNumber *)attribute; +- (void)removeValueForAttribute:(NSNumber *)attribute; + +- (nullable instancetype)initWithDataVersion:(NSNumber * _Nullable)dataVersion attributes:(NSDictionary * _Nullable)attributes; +@end + +NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIP/MTRDeviceClusterData.mm b/src/darwin/Framework/CHIP/MTRDeviceClusterData.mm new file mode 100644 index 00000000000000..fb3d8b38ae027d --- /dev/null +++ b/src/darwin/Framework/CHIP/MTRDeviceClusterData.mm @@ -0,0 +1,122 @@ +/** + * Copyright (c) 2024 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "MTRDeviceClusterData.h" +#import "MTRLogging_Internal.h" +#import "MTRUtilities.h" + +static NSString * const sDataVersionKey = @"dataVersion"; +static NSString * const sAttributesKey = @"attributes"; + +@implementation MTRDeviceClusterData { + NSMutableDictionary * _attributes; +} + +- (void)storeValue:(MTRDeviceDataValueDictionary _Nullable)value forAttribute:(NSNumber *)attribute +{ + _attributes[attribute] = value; +} + +- (void)removeValueForAttribute:(NSNumber *)attribute +{ + [_attributes removeObjectForKey:attribute]; +} + +- (NSDictionary *)attributes +{ + return _attributes; +} + ++ (BOOL)supportsSecureCoding +{ + return YES; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"", _dataVersion, static_cast(_attributes.count)]; +} + +- (nullable instancetype)init +{ + return [self initWithDataVersion:nil attributes:nil]; +} + +// Attributes dictionary is: attributeID => data-value dictionary +- (nullable instancetype)initWithDataVersion:(NSNumber * _Nullable)dataVersion attributes:(NSDictionary * _Nullable)attributes +{ + self = [super init]; + if (self == nil) { + return nil; + } + + _dataVersion = [dataVersion copy]; + _attributes = [NSMutableDictionary dictionaryWithCapacity:attributes.count]; + [_attributes addEntriesFromDictionary:attributes]; + + return self; +} + +- (nullable instancetype)initWithCoder:(NSCoder *)decoder +{ + self = [super init]; + if (self == nil) { + return nil; + } + + _dataVersion = [decoder decodeObjectOfClass:[NSNumber class] forKey:sDataVersionKey]; + if (_dataVersion != nil && ![_dataVersion isKindOfClass:[NSNumber class]]) { + MTR_LOG_ERROR("MTRDeviceClusterData got %@ for data version, not NSNumber.", _dataVersion); + return nil; + } + + static NSSet * const sAttributeValueClasses = [NSSet setWithObjects:[NSDictionary class], [NSArray class], [NSData class], [NSString class], [NSNumber class], nil]; + _attributes = [decoder decodeObjectOfClasses:sAttributeValueClasses forKey:sAttributesKey]; + if (_attributes != nil && ![_attributes isKindOfClass:[NSDictionary class]]) { + MTR_LOG_ERROR("MTRDeviceClusterData got %@ for attributes, not NSDictionary.", _attributes); + return nil; + } + + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:self.dataVersion forKey:sDataVersionKey]; + [coder encodeObject:self.attributes forKey:sAttributesKey]; +} + +- (id)copyWithZone:(NSZone *)zone +{ + return [[MTRDeviceClusterData alloc] initWithDataVersion:_dataVersion attributes:_attributes]; +} + +- (BOOL)isEqualToClusterData:(MTRDeviceClusterData *)otherClusterData +{ + return MTREqualObjects(_dataVersion, otherClusterData.dataVersion) + && MTREqualObjects(_attributes, otherClusterData.attributes); +} + +- (BOOL)isEqual:(id)object +{ + if ([object class] != [self class]) { + return NO; + } + + return [self isEqualToClusterData:object]; +} + +@end diff --git a/src/darwin/Framework/CHIP/MTRDeviceControllerDataStore.h b/src/darwin/Framework/CHIP/MTRDeviceControllerDataStore.h index 826fd1821f7640..508092b40fa613 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceControllerDataStore.h +++ b/src/darwin/Framework/CHIP/MTRDeviceControllerDataStore.h @@ -18,7 +18,9 @@ #import #import #import -#import + +#import "MTRDeviceClusterData.h" +#import "MTRDevice_Internal.h" #include diff --git a/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.mm b/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.mm index eb7881bfd11d21..1a164e50b14b5c 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.mm @@ -1149,7 +1149,7 @@ - (MTRDevice *)_setupDeviceForNodeID:(NSNumber *)nodeID prefetchedClusterData:(N { os_unfair_lock_assert_owner(self.deviceMapLock); - MTRDevice * deviceToReturn = [[MTRDevice_Concrete alloc] initWithNodeID:nodeID controller:self]; + MTRDevice_Concrete * deviceToReturn = [[MTRDevice_Concrete alloc] initWithNodeID:nodeID controller:self]; // If we're not running, don't add the device to our map. That would // create a cycle that nothing would break. Just return the device, // which will be in exactly the state it would be in if it were created @@ -1190,7 +1190,16 @@ - (MTRDevice *)_setupDeviceForNodeID:(NSNumber *)nodeID prefetchedClusterData:(N std::lock_guard lock(*self.deviceMapLock); NSMutableDictionary * deviceAttributeCounts = [NSMutableDictionary dictionary]; for (NSNumber * nodeID in self.nodeIDToDeviceMap) { - deviceAttributeCounts[nodeID] = @([[self.nodeIDToDeviceMap objectForKey:nodeID] unitTestAttributeCount]); + MTRDevice * device = [self.nodeIDToDeviceMap objectForKey:nodeID]; + + // TODO: Can we not just assume this isKindOfClass test is true? Would be + // really nice if we had compile-time checking for this somehow... + if (![device isKindOfClass:MTRDevice_Concrete.class]) { + continue; + } + + auto * concreteDevice = static_cast(device); + deviceAttributeCounts[nodeID] = @([concreteDevice unitTestAttributeCount]); } return deviceAttributeCounts; } @@ -1389,9 +1398,19 @@ - (void)getSessionForNode:(chip::NodeId)nodeID completion:(MTRInternalDeviceConn // Get the corresponding MTRDevice object to determine if the case/subscription pool is to be used MTRDevice * device = [self deviceForNodeID:@(nodeID)]; + // TODO: Can we not just assume this isKindOfClass test is true? Would be + // really nice if we had compile-time checking for this somehow... + if (![device isKindOfClass:MTRDevice_Concrete.class]) { + MTR_LOG_ERROR("%@ somehow has %@ instead of MTRDevice_Concrete for node ID 0x%016llX (%llu)", self, device, nodeID, nodeID); + completion(nullptr, chip::NullOptional, [MTRError errorForCHIPErrorCode:CHIP_ERROR_INCORRECT_STATE], nil); + return; + } + + auto * concreteDevice = static_cast(device); + // In the case that this device is known to use thread, queue this with subscription attempts as well, to // help with throttling Thread traffic. - if ([device deviceUsesThread]) { + if ([concreteDevice deviceUsesThread]) { MTRAsyncWorkItem * workItem = [[MTRAsyncWorkItem alloc] initWithQueue:dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)]; [workItem setReadyHandler:^(id _Nonnull context, NSInteger retryCount, MTRAsyncWorkCompletionBlock _Nonnull workItemCompletion) { MTRInternalDeviceConnectionCallback completionWrapper = ^(chip::Messaging::ExchangeManager * _Nullable exchangeManager, diff --git a/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h b/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h index 8fd6e4cb4bfccc..c26518d532e2ea 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h +++ b/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h @@ -31,6 +31,7 @@ #import #import "MTRBaseDevice.h" +#import "MTRDeviceClusterData.h" #import "MTRDeviceController.h" #import "MTRDeviceControllerDataStore.h" #import "MTRDeviceControllerDelegate.h" diff --git a/src/darwin/Framework/CHIP/MTRDeviceDataValueDictionary.h b/src/darwin/Framework/CHIP/MTRDeviceDataValueDictionary.h new file mode 100644 index 00000000000000..34e46b7ccd7ef3 --- /dev/null +++ b/src/darwin/Framework/CHIP/MTRDeviceDataValueDictionary.h @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2024 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef NSDictionary * MTRDeviceDataValueDictionary; + +NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIP/MTRDevice_Concrete.h b/src/darwin/Framework/CHIP/MTRDevice_Concrete.h index 70dac9a362b10d..f2596b8ca4afc7 100644 --- a/src/darwin/Framework/CHIP/MTRDevice_Concrete.h +++ b/src/darwin/Framework/CHIP/MTRDevice_Concrete.h @@ -18,6 +18,7 @@ #import #import +#import "MTRDeviceClusterData.h" #import "MTRDeviceController_Concrete.h" NS_ASSUME_NONNULL_BEGIN @@ -31,6 +32,23 @@ NS_ASSUME_NONNULL_BEGIN // false-positives, for example due to compressed fabric id collisions. - (void)nodeMayBeAdvertisingOperational; +// Method to insert persisted cluster data +// Contains data version information and attribute values. +- (void)setPersistedClusterData:(NSDictionary *)clusterData; + +// Method to insert persisted data that pertains to the whole device. +- (void)setPersistedDeviceData:(NSDictionary *)data; + +// Returns whether this MTRDevice_Concrete uses Thread for communication +- (BOOL)deviceUsesThread; + +// For use from MTRDeviceController_Concrete when setting up a device instance. +- (void)setStorageBehaviorConfiguration:(MTRDeviceStorageBehaviorConfiguration *)storageBehaviorConfiguration; + +#ifdef DEBUG +- (NSUInteger)unitTestAttributeCount; +#endif + @end NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIP/MTRDevice_Concrete.mm b/src/darwin/Framework/CHIP/MTRDevice_Concrete.mm index 2342cadfb253f5..48d7f7f99d0fe7 100644 --- a/src/darwin/Framework/CHIP/MTRDevice_Concrete.mm +++ b/src/darwin/Framework/CHIP/MTRDevice_Concrete.mm @@ -31,6 +31,7 @@ #import "MTRDeviceConnectivityMonitor.h" #import "MTRDeviceControllerOverXPC.h" #import "MTRDeviceController_Internal.h" +#import "MTRDeviceDataValueDictionary.h" #import "MTRDevice_Concrete.h" #import "MTRDevice_Internal.h" #import "MTRError_Internal.h" @@ -55,6 +56,8 @@ #import #import +static NSString * const sLastInitialSubscribeLatencyKey = @"lastInitialSubscribeLatency"; + // allow readwrite access to superclass properties @interface MTRDevice_Concrete () @@ -208,7 +211,6 @@ @interface MTRDevice_Concrete () // whatever lock protects the time sync bits. @property (nonatomic, readonly) os_unfair_lock timeSyncLock; -@property (nonatomic) chip::FabricIndex fabricIndex; @property (nonatomic) NSMutableArray *> * unreportedEvents; @property (nonatomic) BOOL receivingReport; @property (nonatomic) BOOL receivingPrimingReport; @@ -367,7 +369,6 @@ - (instancetype)initWithNodeID:(NSNumber *)nodeID controller:(MTRDeviceControlle if (self = [super initForSubclassesWithNodeID:nodeID controller:controller]) { _timeSyncLock = OS_UNFAIR_LOCK_INIT; _descriptionLock = OS_UNFAIR_LOCK_INIT; - _fabricIndex = controller.fabricIndex; _queue = dispatch_queue_create("org.csa-iot.matter.framework.device.workqueue", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL); _expectedValueCache = [NSMutableDictionary dictionary]; diff --git a/src/darwin/Framework/CHIP/MTRDevice_Internal.h b/src/darwin/Framework/CHIP/MTRDevice_Internal.h index 2cdcd66976099a..f954979f131084 100644 --- a/src/darwin/Framework/CHIP/MTRDevice_Internal.h +++ b/src/darwin/Framework/CHIP/MTRDevice_Internal.h @@ -28,8 +28,6 @@ NS_ASSUME_NONNULL_BEGIN @class MTRAsyncWorkQueue; -typedef NSDictionary * MTRDeviceDataValueDictionary; - typedef void (^MTRDevicePerformAsyncBlock)(MTRBaseDevice * baseDevice); typedef NS_ENUM(NSUInteger, MTRInternalDeviceState) { @@ -52,20 +50,6 @@ typedef NS_ENUM(NSUInteger, MTRInternalDeviceState) { MTRInternalDeviceStateLaterSubscriptionEstablished = 4, }; -/** - * Information about a cluster: data version and known attribute values. - */ -MTR_TESTABLE -@interface MTRDeviceClusterData : NSObject -@property (nonatomic, nullable) NSNumber * dataVersion; -@property (nonatomic, readonly) NSDictionary * attributes; // attributeID => data-value dictionary - -- (void)storeValue:(MTRDeviceDataValueDictionary _Nullable)value forAttribute:(NSNumber *)attribute; -- (void)removeValueForAttribute:(NSNumber *)attribute; - -- (nullable instancetype)initWithDataVersion:(NSNumber * _Nullable)dataVersion attributes:(NSDictionary * _Nullable)attributes; -@end - // Consider moving utility classes to their own file #pragma mark - Utility Classes @@ -157,22 +141,6 @@ MTR_DIRECT_MEMBERS @property (nonatomic) dispatch_queue_t queue; @property (nonatomic, readonly) MTRAsyncWorkQueue * asyncWorkQueue; -// Method to insert persisted cluster data -// Contains data version information and attribute values. -- (void)setPersistedClusterData:(NSDictionary *)clusterData; - -// Method to insert persisted data that pertains to the whole device. -- (void)setPersistedDeviceData:(NSDictionary *)data; - -#ifdef DEBUG -- (NSUInteger)unitTestAttributeCount; -#endif - -- (void)setStorageBehaviorConfiguration:(MTRDeviceStorageBehaviorConfiguration *)storageBehaviorConfiguration; - -// Returns whether this MTRDevice uses Thread for communication -- (BOOL)deviceUsesThread; - #pragma mark - MTRDevice functionality to deal with delegates. // Returns YES if any non-null delegates were found @@ -208,11 +176,6 @@ MTR_DIRECT_MEMBERS static NSString * const kDefaultSubscriptionPoolSizeOverrideKey = @"subscriptionPoolSizeOverride"; static NSString * const kTestStorageUserDefaultEnabledKey = @"enableTestStorage"; -// ex-MTRDeviceClusterData constants -static NSString * const sDataVersionKey = @"dataVersion"; -static NSString * const sAttributesKey = @"attributes"; -static NSString * const sLastInitialSubscribeLatencyKey = @"lastInitialSubscribeLatency"; - // Declared inside platform, but noting here for reference // static NSString * const kSRPTimeoutInMsecsUserDefaultKey = @"SRPTimeoutInMSecsOverride"; diff --git a/src/darwin/Framework/CHIP/MTRDevice_XPC.mm b/src/darwin/Framework/CHIP/MTRDevice_XPC.mm index aefa2fffc1eeb7..7782509fb79bcd 100644 --- a/src/darwin/Framework/CHIP/MTRDevice_XPC.mm +++ b/src/darwin/Framework/CHIP/MTRDevice_XPC.mm @@ -41,6 +41,7 @@ #import "MTRDeviceControllerStartupParams_Internal.h" #import "MTRDeviceController_Concrete.h" #import "MTRDeviceController_XPC.h" +#import "MTRDeviceDataValueDictionary.h" #import "MTRDevice_Concrete.h" #import "MTRDevice_Internal.h" #import "MTRDevice_XPC_Internal.h" diff --git a/src/darwin/Framework/CHIPTests/MTRDeviceTests.m b/src/darwin/Framework/CHIPTests/MTRDeviceTests.m index 8a3188cbeb7db1..5fb3549a49216a 100644 --- a/src/darwin/Framework/CHIPTests/MTRDeviceTests.m +++ b/src/darwin/Framework/CHIPTests/MTRDeviceTests.m @@ -25,6 +25,7 @@ #import #import "MTRCommandPayloadExtensions_Internal.h" +#import "MTRDeviceClusterData.h" #import "MTRDeviceControllerLocalTestStorage.h" #import "MTRDeviceStorageBehaviorConfiguration.h" #import "MTRDeviceTestDelegate.h" diff --git a/src/darwin/Framework/CHIPTests/MTRPerControllerStorageTests.m b/src/darwin/Framework/CHIPTests/MTRPerControllerStorageTests.m index 6b193c7b55dbb5..721cb09b994e25 100644 --- a/src/darwin/Framework/CHIPTests/MTRPerControllerStorageTests.m +++ b/src/darwin/Framework/CHIPTests/MTRPerControllerStorageTests.m @@ -19,7 +19,9 @@ #import #import +#import "MTRDeviceClusterData.h" #import "MTRDeviceControllerLocalTestStorage.h" +#import "MTRDeviceDataValueDictionary.h" #import "MTRDeviceStorageBehaviorConfiguration.h" #import "MTRDeviceTestDelegate.h" #import "MTRDevice_Internal.h" diff --git a/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestDeclarations.h b/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestDeclarations.h index 22998eb4496216..74a7dc280b1216 100644 --- a/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestDeclarations.h +++ b/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestDeclarations.h @@ -18,14 +18,14 @@ #import #import -// For MTRDeviceDataValueDictionary: +#import "MTRDeviceClusterData.h" +#import "MTRDeviceDataValueDictionary.h" #import "MTRDevice_Internal.h" NS_ASSUME_NONNULL_BEGIN #pragma mark - Declarations for internal methods -@class MTRDeviceClusterData; // MTRDeviceControllerDataStore.h includes C++ header, and so we need to declare the methods separately @protocol MTRDeviceControllerDataStoreAttributeStoreMethods - (nullable NSDictionary *)getStoredClusterDataForNodeID:(NSNumber *)nodeID; @@ -51,6 +51,7 @@ NS_ASSUME_NONNULL_BEGIN @interface MTRDevice (Test) - (BOOL)_attributeDataValue:(NSDictionary *)one isEqualToDataValue:(NSDictionary *)theOther; - (NSMutableArray *)arrayOfNumbersFromAttributeValue:(MTRDeviceDataValueDictionary)dataDictionary; +- (void)setStorageBehaviorConfiguration:(MTRDeviceStorageBehaviorConfiguration *)storageBehaviorConfiguration; @end #pragma mark - Declarations for items compiled only for DEBUG configuration @@ -85,6 +86,7 @@ NS_ASSUME_NONNULL_BEGIN - (MTRDeviceClusterData *)unitTestGetClusterDataForPath:(MTRClusterPath *)path; - (NSSet *)unitTestGetPersistedClusters; - (BOOL)unitTestClusterHasBeenPersisted:(MTRClusterPath *)path; +- (NSUInteger)unitTestAttributeCount; @end #endif diff --git a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj index ea5ef7969a1d4b..bd05397c05c67e 100644 --- a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj +++ b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj @@ -132,6 +132,9 @@ 5109E9B42CB8B5DF0006884B /* MTRDeviceType.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5109E9B32CB8B5DF0006884B /* MTRDeviceType.mm */; }; 5109E9B52CB8B5DF0006884B /* MTRDeviceType.h in Headers */ = {isa = PBXBuildFile; fileRef = 5109E9B22CB8B5DF0006884B /* MTRDeviceType.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5109E9B72CB8B83D0006884B /* MTRDeviceTypeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5109E9B62CB8B83D0006884B /* MTRDeviceTypeTests.m */; }; + 5109E9BA2CC1F23E0006884B /* MTRDeviceClusterData.h in Headers */ = {isa = PBXBuildFile; fileRef = 5109E9B82CC1F23E0006884B /* MTRDeviceClusterData.h */; }; + 5109E9BB2CC1F23E0006884B /* MTRDeviceClusterData.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5109E9B92CC1F23E0006884B /* MTRDeviceClusterData.mm */; }; + 5109E9BD2CC1F25C0006884B /* MTRDeviceDataValueDictionary.h in Headers */ = {isa = PBXBuildFile; fileRef = 5109E9BC2CC1F25C0006884B /* MTRDeviceDataValueDictionary.h */; }; 510A07492A685D3900A9241C /* Matter.apinotes in Headers */ = {isa = PBXBuildFile; fileRef = 510A07482A685D3900A9241C /* Matter.apinotes */; settings = {ATTRIBUTES = (Public, ); }; }; 510CECA8297F72970064E0B3 /* MTROperationalCertificateIssuerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 510CECA6297F72470064E0B3 /* MTROperationalCertificateIssuerTests.m */; }; 5117DD3829A931AE00FFA1AA /* MTROperationalBrowser.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5117DD3629A931AD00FFA1AA /* MTROperationalBrowser.mm */; }; @@ -584,6 +587,9 @@ 5109E9B22CB8B5DF0006884B /* MTRDeviceType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTRDeviceType.h; sourceTree = ""; }; 5109E9B32CB8B5DF0006884B /* MTRDeviceType.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRDeviceType.mm; sourceTree = ""; }; 5109E9B62CB8B83D0006884B /* MTRDeviceTypeTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MTRDeviceTypeTests.m; sourceTree = ""; }; + 5109E9B82CC1F23E0006884B /* MTRDeviceClusterData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTRDeviceClusterData.h; sourceTree = ""; }; + 5109E9B92CC1F23E0006884B /* MTRDeviceClusterData.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRDeviceClusterData.mm; sourceTree = ""; }; + 5109E9BC2CC1F25C0006884B /* MTRDeviceDataValueDictionary.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTRDeviceDataValueDictionary.h; sourceTree = ""; }; 510A07482A685D3900A9241C /* Matter.apinotes */ = {isa = PBXFileReference; lastKnownFileType = text.apinotes; name = Matter.apinotes; path = CHIP/Matter.apinotes; sourceTree = ""; }; 510CECA6297F72470064E0B3 /* MTROperationalCertificateIssuerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTROperationalCertificateIssuerTests.m; sourceTree = ""; }; 5117DD3629A931AD00FFA1AA /* MTROperationalBrowser.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTROperationalBrowser.mm; sourceTree = ""; }; @@ -1383,6 +1389,8 @@ 7534F12628BFF20300390851 /* MTRDeviceAttestationDelegate.mm */, 88EBF8CD27FABDD500686BC1 /* MTRDeviceAttestationDelegateBridge.h */, 88EBF8CC27FABDD500686BC1 /* MTRDeviceAttestationDelegateBridge.mm */, + 5109E9B82CC1F23E0006884B /* MTRDeviceClusterData.h */, + 5109E9B92CC1F23E0006884B /* MTRDeviceClusterData.mm */, 2C5EEEF4268A85C400CAE3D3 /* MTRDeviceConnectionBridge.h */, 2C5EEEF5268A85C400CAE3D3 /* MTRDeviceConnectionBridge.mm */, 75B3269B2BCDB9D600E17C4E /* MTRDeviceConnectivityMonitor.h */, @@ -1417,6 +1425,7 @@ 51565CB32A7AD78D00469F18 /* MTRDeviceControllerStorageDelegate.h */, 5A6FEC9427B5976200F25F42 /* MTRDeviceControllerXPCConnection.h */, 5A6FEC9527B5983000F25F42 /* MTRDeviceControllerXPCConnection.mm */, + 5109E9BC2CC1F25C0006884B /* MTRDeviceDataValueDictionary.h */, 5A6FEC8B27B5609C00F25F42 /* MTRDeviceOverXPC.h */, 5A6FEC9727B5C6AF00F25F42 /* MTRDeviceOverXPC.mm */, 754784632BFE65B70089C372 /* MTRDeviceStorageBehaviorConfiguration.h */, @@ -1698,6 +1707,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 5109E9BD2CC1F25C0006884B /* MTRDeviceDataValueDictionary.h in Headers */, 51D0B1282B617246006E3511 /* MTRServerEndpoint.h in Headers */, 51565CB62A7B0D6600469F18 /* MTRDeviceControllerParameters.h in Headers */, 51565CB42A7AD78D00469F18 /* MTRDeviceControllerStorageDelegate.h in Headers */, @@ -1744,6 +1754,7 @@ 2C222AD0255C620600E446B9 /* MTRBaseDevice.h in Headers */, 7596A84F2877E6A9004DAE0E /* MTRCluster_Internal.h in Headers */, 9B5CCB612C6EE29E009DD99B /* MTRDeviceControllerXPCParameters.h in Headers */, + 5109E9BA2CC1F23E0006884B /* MTRDeviceClusterData.h in Headers */, 991DC0842475F45400C13860 /* MTRDeviceController.h in Headers */, 88FA798D2B7B257100CD4B6F /* MTRMetricsCollector.h in Headers */, 9BFE5D502C6D3075007D4319 /* MTRDeviceController_XPC.h in Headers */, @@ -2120,6 +2131,7 @@ 511913FB28C100EF009235E9 /* MTRBaseSubscriptionCallback.mm in Sources */, 510470FB2A2F7DF60053EA7E /* MTRBackwardsCompatShims.mm in Sources */, 5173A47629C0E2ED00F67F48 /* MTRFabricInfo.mm in Sources */, + 5109E9BB2CC1F23E0006884B /* MTRDeviceClusterData.mm in Sources */, 9B231B052C62EF650030EB37 /* MTRDeviceController_Concrete.mm in Sources */, 5ACDDD7D27CD16D200EFD68A /* MTRClusterStateCacheContainer.mm in Sources */, 75B3269E2BCDB9EA00E17C4E /* MTRDeviceConnectivityMonitor.mm in Sources */,