diff --git a/src/darwin/Framework/CHIP/MTRDevice.mm b/src/darwin/Framework/CHIP/MTRDevice.mm index 493d3a0cb18a95..83d25f06053c04 100644 --- a/src/darwin/Framework/CHIP/MTRDevice.mm +++ b/src/darwin/Framework/CHIP/MTRDevice.mm @@ -153,6 +153,43 @@ typedef NS_ENUM(NSUInteger, MTRDeviceWorkItemDuplicateTypeID) { MTRDeviceWorkItemDuplicateReadTypeID = 1, }; +@implementation MTRDeviceClusterData + +static NSString * const sDataVersionKey = @"dataVersion"; + ++ (BOOL)supportsSecureCoding +{ + return YES; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"", _dataVersion]; +} + +- (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; + } + + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:self.dataVersion forKey:sDataVersionKey]; +} + +@end + @interface MTRDevice () @property (nonatomic, readonly) os_unfair_lock lock; // protects the caches and device state // protects against concurrent time updates by guarding timeUpdateScheduled flag which manages time updates scheduling, @@ -228,6 +265,8 @@ @implementation MTRDevice { NSUInteger _unitTestAttributesReportedSinceLastCheck; #endif BOOL _delegateDeviceCachePrimedCalled; + NSMutableDictionary * _clusterData; + NSMutableDictionary * _clusterDataToPersist; } - (instancetype)initWithNodeID:(NSNumber *)nodeID controller:(MTRDeviceController *)controller @@ -244,6 +283,7 @@ - (instancetype)initWithNodeID:(NSNumber *)nodeID controller:(MTRDeviceControlle _expectedValueCache = [NSMutableDictionary dictionary]; _asyncWorkQueue = [[MTRAsyncWorkQueue alloc] initWithContext:self]; _state = MTRDeviceStateUnknown; + _clusterData = [NSMutableDictionary dictionary]; MTR_LOG_INFO("%@ init with hex nodeID 0x%016llX", self, _nodeID.unsignedLongLongValue); } return self; @@ -777,6 +817,13 @@ - (void)_handleReportEnd _receivingPrimingReport = NO; _estimatedStartTimeFromGeneralDiagnosticsUpTime = nil; + BOOL dataStoreExists = _deviceController.controllerDataStore != nil; + if (dataStoreExists && _clusterDataToPersist.count) { + MTR_LOG_DEFAULT("%@ Storing cluster information (data version) count: %lu", self, static_cast(_clusterDataToPersist.count)); + [_deviceController.controllerDataStore storeClusterData:_clusterDataToPersist forNodeID:_nodeID]; + _clusterDataToPersist = nil; + } + // For unit testing only #ifdef DEBUG id delegate = _weakDelegate.strongObject; @@ -924,18 +971,10 @@ - (void)_handleEventReport:(NSArray *> *)eventRepor NSMutableDictionary * dataVersions = [NSMutableDictionary dictionary]; std::lock_guard lock(_lock); - for (MTRAttributePath * path in _readCache) { - NSDictionary * dataValue = _readCache[path]; - NSNumber * dataVersionNumber = dataValue[MTRDataVersionKey]; - if (dataVersionNumber) { - MTRClusterPath * clusterPath = [MTRClusterPath clusterPathWithEndpointID:path.endpoint clusterID:path.cluster]; - NSNumber * currentDataVersion = dataVersions[clusterPath]; - // Use the highest data version - if (currentDataVersion.unsignedLongValue < dataVersionNumber.unsignedLongValue) { - dataVersions[clusterPath] = dataVersionNumber; - } - } + for (MTRClusterPath * path in _clusterData) { + dataVersions[path] = _clusterData[path].dataVersion; } + MTR_LOG_INFO("%@ _getCachedDataVersions dataVersions count: %lu readCache count: %lu", self, static_cast(dataVersions.count), static_cast(_readCache.count)); return dataVersions; @@ -1873,9 +1912,45 @@ - (void)_performScheduledExpirationCheck - (BOOL)_attributeDataValue:(NSDictionary *)one isEqualToDataValue:(NSDictionary *)theOther { - // Attribute data-value dictionary should be all standard containers which - // means isEqual: comparisons all the way down, making a deep comparison. - return [one isEqualToDictionary:theOther]; + // Attribute data-value dictionaries are equal if type and value are equal + return [one[MTRTypeKey] isEqual:theOther[MTRTypeKey]] && (!one[MTRValueKey] || [one[MTRValueKey] isEqual:theOther[MTRValueKey]]); +} + +// Utility to return data value dictionary without data version +- (NSDictionary *)_dataValueWithoutDataVersion:(NSDictionary *)attributeValue; +{ + if (attributeValue[MTRValueKey]) { + return @{ MTRTypeKey : attributeValue[MTRTypeKey], MTRValueKey : attributeValue[MTRValueKey] }; + } else { + return @{ MTRTypeKey : attributeValue[MTRTypeKey] }; + } +} + +// Update cluster data version and also note the change, so at onReportEnd it can be persisted +- (void)_updateDataVersion:(NSNumber *)dataVersion forClusterPath:(MTRClusterPath *)clusterPath +{ + BOOL dataVersionChanged = NO; + MTRDeviceClusterData * clusterData = _clusterData[clusterPath]; + if (!clusterData) { + clusterData = [[MTRDeviceClusterData alloc] init]; + _clusterData[clusterPath] = clusterData; + dataVersionChanged = YES; + } else if (![clusterData.dataVersion isEqualToNumber:dataVersion]) { + dataVersionChanged = YES; + } + + if (dataVersionChanged) { + clusterData.dataVersion = dataVersion; + + // Set up for persisting if there is data store + BOOL dataStoreExists = _deviceController.controllerDataStore != nil; + if (dataStoreExists) { + if (!_clusterDataToPersist) { + _clusterDataToPersist = [NSMutableDictionary dictionary]; + } + _clusterDataToPersist[clusterPath] = clusterData; + } + } } // assume lock is held @@ -1919,6 +1994,14 @@ - (NSArray *)_getAttributesToReportWithReportedValues:(NSArray *)attributeValues reportChan } } +- (void)setClusterData:(NSDictionary *)clusterData +{ + [_clusterData addEntriesFromDictionary:clusterData]; +} + - (BOOL)deviceCachePrimed { std::lock_guard lock(_lock); diff --git a/src/darwin/Framework/CHIP/MTRDeviceController.mm b/src/darwin/Framework/CHIP/MTRDeviceController.mm index ae6669cdab5add..d51b5cd7712b5c 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceController.mm @@ -935,6 +935,11 @@ - (MTRDevice *)deviceForNodeID:(NSNumber *)nodeID if (attributesFromCache.count) { [deviceToReturn setAttributeValues:attributesFromCache reportChanges:NO]; } + NSDictionary * clusterData = [_controllerDataStore getStoredClusterDataForNodeID:nodeID]; + MTR_LOG_INFO("Loaded %lu cluster data from storage for %@", static_cast(clusterData.count), deviceToReturn); + if (clusterData.count) { + [deviceToReturn setClusterData:clusterData]; + } } return deviceToReturn; diff --git a/src/darwin/Framework/CHIP/MTRDeviceControllerDataStore.h b/src/darwin/Framework/CHIP/MTRDeviceControllerDataStore.h index 8df10f63e1d27d..90007dbb2df861 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceControllerDataStore.h +++ b/src/darwin/Framework/CHIP/MTRDeviceControllerDataStore.h @@ -17,6 +17,7 @@ #import #import #import +#import #if MTR_PER_CONTROLLER_STORAGE_ENABLED #import #else @@ -67,7 +68,9 @@ NS_ASSUME_NONNULL_BEGIN * Storage for MTRDevice attribute read cache. This is local-only storage as an optimization. New controller devices using MTRDevice API can prime their own local cache from devices directly. */ - (nullable NSArray *)getStoredAttributesForNodeID:(NSNumber *)nodeID; +- (nullable NSDictionary *)getStoredClusterDataForNodeID:(NSNumber *)nodeID; - (void)storeAttributeValues:(NSArray *)dataValues forNodeID:(NSNumber *)nodeID; +- (void)storeClusterData:(NSDictionary *)clusterData forNodeID:(NSNumber *)nodeID; - (void)clearStoredAttributesForNodeID:(NSNumber *)nodeID; - (void)clearAllStoredAttributes; diff --git a/src/darwin/Framework/CHIP/MTRDeviceControllerDataStore.mm b/src/darwin/Framework/CHIP/MTRDeviceControllerDataStore.mm index 26d68921591f75..10598325eaa179 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceControllerDataStore.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceControllerDataStore.mm @@ -398,6 +398,28 @@ - (BOOL)_deleteClusterIndexForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)e return [self _removeAttributeCacheValueForKey:[self _clusterIndexKeyForNodeID:nodeID endpointID:endpointID]]; } +static NSString * sAttributeCacheClusterDataKeyPrefix = @"attrCacheClusterData"; + +- (NSString *)_clusterDataKeyForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID +{ + return [sAttributeCacheClusterDataKeyPrefix stringByAppendingFormat:@":0x%016llX:%0x04X:0x%08lX", nodeID.unsignedLongLongValue, endpointID.unsignedShortValue, clusterID.unsignedLongValue]; +} + +- (nullable MTRDeviceClusterData *)_fetchClusterDataForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID +{ + return [self _fetchAttributeCacheValueForKey:[self _clusterDataKeyForNodeID:nodeID endpointID:endpointID clusterID:clusterID] expectedClass:[MTRDeviceClusterData class]]; +} + +- (BOOL)_storeClusterData:(MTRDeviceClusterData *)clusterData forNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID +{ + return [self _storeAttributeCacheValue:clusterData forKey:[self _clusterDataKeyForNodeID:nodeID endpointID:endpointID clusterID:clusterID]]; +} + +- (BOOL)_deleteClusterDataForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID +{ + return [self _removeAttributeCacheValueForKey:[self _clusterDataKeyForNodeID:nodeID endpointID:endpointID clusterID:clusterID]]; +} + static NSString * sAttributeCacheAttributeIndexKeyPrefix = @"attrCacheAttributeIndex"; - (NSString *)_attributeIndexKeyForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID @@ -442,8 +464,14 @@ - (BOOL)_deleteAttributeValueForNodeID:(NSNumber *)nodeID endpointID:(NSNumber * return [self _removeAttributeCacheValueForKey:[self _attributeValueKeyForNodeID:nodeID endpointID:endpointID clusterID:clusterID attributeID:attributeID]]; } +#pragma - Attribute Cache versions + +#define ATTRIBUTE_CACHE_VERSION 1 + #pragma - Attribute Cache management +#define ATTRIBUTE_CACHE_VERBOSE_LOGGING 1 + #ifndef ATTRIBUTE_CACHE_VERBOSE_LOGGING #define ATTRIBUTE_CACHE_VERBOSE_LOGGING 0 #endif @@ -565,16 +593,22 @@ - (void)_pruneEmptyStoredAttributesBranches if (attributeIndex.count != attributeIndexCopy.count) { BOOL success; + BOOL clusterDataSuccess = YES; if (attributeIndexCopy.count) { success = [self _storeAttributeIndex:attributeIndexCopy forNodeID:nodeID endpointID:endpointID clusterID:clusterID]; } else { [clusterIndexCopy removeObject:clusterID]; success = [self _deleteAttributeIndexForNodeID:nodeID endpointID:endpointID clusterID:clusterID]; + clusterDataSuccess = [self _deleteClusterDataForNodeID:nodeID endpointID:endpointID clusterID:clusterID]; } if (!success) { storeFailures++; MTR_LOG_INFO("Store failed in _pruneEmptyStoredAttributesBranches for attributeIndex (%lu) @ node 0x%016llX endpoint %u cluster 0x%08lX", static_cast(attributeIndexCopy.count), nodeID.unsignedLongLongValue, endpointID.unsignedShortValue, clusterID.unsignedLongValue); } + if (!clusterDataSuccess) { + storeFailures++; + MTR_LOG_INFO("Store failed in _pruneEmptyStoredAttributesBranches for clusterData @ node 0x%016llX endpoint %u cluster 0x%08lX", nodeID.unsignedLongLongValue, endpointID.unsignedShortValue, clusterID.unsignedLongValue); + } } } @@ -719,9 +753,11 @@ - (void)_clearStoredAttributesForNodeID:(NSNumber *)nodeID { NSUInteger endpointsClearAttempts = 0; NSUInteger clustersClearAttempts = 0; + NSUInteger clusterDataClearAttempts = 0; NSUInteger attributesClearAttempts = 0; NSUInteger endpointsCleared = 0; NSUInteger clustersCleared = 0; + NSUInteger clusterDataCleared = 0; NSUInteger attributesCleared = 0; // Fetch endpoint index @@ -733,6 +769,7 @@ - (void)_clearStoredAttributesForNodeID:(NSNumber *)nodeID NSArray * clusterIndex = [self _fetchClusterIndexForNodeID:nodeID endpointID:endpointID]; clustersClearAttempts += clusterIndex.count; + clusterDataClearAttempts += clusterIndex.count; // Assuming every cluster has cluster data for (NSNumber * clusterID in clusterIndex) { // Fetch attribute index NSArray * attributeIndex = [self _fetchAttributeIndexForNodeID:nodeID endpointID:endpointID clusterID:clusterID]; @@ -753,6 +790,13 @@ - (void)_clearStoredAttributesForNodeID:(NSNumber *)nodeID } else { clustersCleared++; } + + success = [self _deleteClusterDataForNodeID:nodeID endpointID:endpointID clusterID:clusterID]; + if (!success) { + MTR_LOG_INFO("Delete failed for clusterData @ node 0x%016llX endpoint %u cluster 0x%08lX", nodeID.unsignedLongLongValue, endpointID.unsignedShortValue, clusterID.unsignedLongValue); + } else { + clusterDataCleared++; + } } BOOL success = [self _deleteClusterIndexForNodeID:nodeID endpointID:endpointID]; @@ -768,7 +812,7 @@ - (void)_clearStoredAttributesForNodeID:(NSNumber *)nodeID MTR_LOG_INFO("Delete failed for endpointrIndex @ node 0x%016llX", nodeID.unsignedLongLongValue); } - MTR_LOG_INFO("clearStoredAttributesForNodeID: deleted endpoints %lu/%lu clusters %lu/%lu attributes %lu/%lu", static_cast(endpointsCleared), static_cast(endpointsClearAttempts), static_cast(clustersCleared), static_cast(clustersClearAttempts), static_cast(attributesCleared), static_cast(attributesClearAttempts)); + MTR_LOG_INFO("clearStoredAttributesForNodeID: deleted endpoints %lu/%lu clusters %lu/%lu clusterData %lu/%lu attributes %lu/%lu", static_cast(endpointsCleared), static_cast(endpointsClearAttempts), static_cast(clustersCleared), static_cast(clustersClearAttempts), static_cast(clusterDataCleared), static_cast(clusterDataClearAttempts), static_cast(attributesCleared), static_cast(attributesClearAttempts)); } - (void)clearStoredAttributesForNodeID:(NSNumber *)nodeID @@ -809,6 +853,140 @@ - (void)clearAllStoredAttributes }); } +- (nullable NSDictionary *)getStoredClusterDataForNodeID:(NSNumber *)nodeID +{ + __block NSMutableDictionary * clusterDataToReturn = nil; + dispatch_sync(_storageDelegateQueue, ^{ + // Fetch node index + NSArray * nodeIndex = [self _fetchNodeIndex]; + +#if ATTRIBUTE_CACHE_VERBOSE_LOGGING + MTR_LOG_INFO("Fetch got %lu values for nodeIndex", static_cast(nodeIndex.count)); +#endif + + if (![nodeIndex containsObject:nodeID]) { + // Sanity check and delete if nodeID exists in index + NSArray * endpointIndex = [self _fetchEndpointIndexForNodeID:nodeID]; + if (endpointIndex) { + MTR_LOG_ERROR("Persistent attribute cache contains orphaned entry for nodeID %@ - deleting", nodeID); + [self clearStoredAttributesForNodeID:nodeID]; + } + + MTR_LOG_INFO("Fetch got no value for endpointIndex @ node 0x%016llX", nodeID.unsignedLongLongValue); + clusterDataToReturn = nil; + return; + } + + // Fetch endpoint index + NSArray * endpointIndex = [self _fetchEndpointIndexForNodeID:nodeID]; + +#if ATTRIBUTE_CACHE_VERBOSE_LOGGING + MTR_LOG_INFO("Fetch got %lu values for endpointIndex @ node 0x%016llX", static_cast(endpointIndex.count), nodeID.unsignedLongLongValue); +#endif + + for (NSNumber * endpointID in endpointIndex) { + // Fetch endpoint index + NSArray * clusterIndex = [self _fetchClusterIndexForNodeID:nodeID endpointID:endpointID]; + +#if ATTRIBUTE_CACHE_VERBOSE_LOGGING + MTR_LOG_INFO("Fetch got %lu values for clusterIndex @ node 0x%016llX %u", static_cast(clusterIndex.count), nodeID.unsignedLongLongValue, endpointID.unsignedShortValue); +#endif + + for (NSNumber * clusterID in clusterIndex) { + // Fetch cluster data + MTRDeviceClusterData * clusterData = [self _fetchClusterDataForNodeID:nodeID endpointID:endpointID clusterID:clusterID]; + if (!clusterData) { + MTR_LOG_INFO("Fetch got no value for clusterData @ node 0x%016llX endpoint %u cluster 0x%08lX", nodeID.unsignedLongLongValue, endpointID.unsignedShortValue, clusterID.unsignedLongValue); + continue; + } + +#if ATTRIBUTE_CACHE_VERBOSE_LOGGING + MTR_LOG_INFO("Fetch got clusterData @ node 0x%016llX endpoint %u cluster 0x%08lX", nodeID.unsignedLongLongValue, endpointID.unsignedShortValue, clusterID.unsignedLongValue); +#endif + + MTRClusterPath * path = [MTRClusterPath clusterPathWithEndpointID:endpointID clusterID:clusterID]; + if (!clusterDataToReturn) { + clusterDataToReturn = [NSMutableDictionary dictionary]; + } + clusterDataToReturn[path] = clusterData; + } + } + }); + + return clusterDataToReturn; +} + +- (void)storeClusterData:(NSDictionary *)clusterData forNodeID:(NSNumber *)nodeID +{ + dispatch_async(_storageDelegateQueue, ^{ + NSUInteger storeFailures = 0; + + for (MTRClusterPath * path in clusterData) { + MTRDeviceClusterData * data = clusterData[path]; + +#if ATTRIBUTE_CACHE_VERBOSE_LOGGING + MTR_LOG_INFO("Attempt to store clusterData @ node 0x%016llX endpoint %u cluster 0x%08lX", nodeID.unsignedLongLongValue, path.endpoint.unsignedShortValue, path.cluster.unsignedLongValue); +#endif + + BOOL storeFailed = NO; + // Ensure node index exists + NSArray * nodeIndex = [self _fetchNodeIndex]; + if (!nodeIndex) { + nodeIndex = [NSArray arrayWithObject:nodeID]; + storeFailed = ![self _storeNodeIndex:nodeIndex]; + } else if (![nodeIndex containsObject:nodeID]) { + storeFailed = ![self _storeNodeIndex:[nodeIndex arrayByAddingObject:nodeID]]; + } + if (storeFailed) { + storeFailures++; + MTR_LOG_INFO("Store failed for nodeIndex"); + continue; + } + + // Ensure endpoint index exists + NSArray * endpointIndex = [self _fetchEndpointIndexForNodeID:nodeID]; + if (!endpointIndex) { + endpointIndex = [NSArray arrayWithObject:path.endpoint]; + storeFailed = ![self _storeEndpointIndex:endpointIndex forNodeID:nodeID]; + } else if (![endpointIndex containsObject:path.endpoint]) { + storeFailed = ![self _storeEndpointIndex:[endpointIndex arrayByAddingObject:path.endpoint] forNodeID:nodeID]; + } + if (storeFailed) { + storeFailures++; + MTR_LOG_INFO("Store failed for endpointIndex @ node 0x%016llX", nodeID.unsignedLongLongValue); + continue; + } + + // Ensure cluster index exists + NSArray * clusterIndex = [self _fetchClusterIndexForNodeID:nodeID endpointID:path.endpoint]; + if (!clusterIndex) { + clusterIndex = [NSArray arrayWithObject:path.cluster]; + storeFailed = ![self _storeClusterIndex:clusterIndex forNodeID:nodeID endpointID:path.endpoint]; + } else if (![clusterIndex containsObject:path.cluster]) { + storeFailed = ![self _storeClusterIndex:[clusterIndex arrayByAddingObject:path.cluster] forNodeID:nodeID endpointID:path.endpoint]; + } + if (storeFailed) { + storeFailures++; + MTR_LOG_INFO("Store failed for clusterIndex @ node 0x%016llX endpoint %u", nodeID.unsignedLongLongValue, path.endpoint.unsignedShortValue); + continue; + } + + // Store cluster data + storeFailed = ![self _storeClusterData:data forNodeID:nodeID endpointID:path.endpoint clusterID:path.cluster]; + if (storeFailed) { + storeFailures++; + MTR_LOG_INFO("Store failed for clusterDAta @ node 0x%016llX endpoint %u cluster 0x%08lX", nodeID.unsignedLongLongValue, path.endpoint.unsignedShortValue, path.cluster.unsignedLongValue); + } + } + + // In the rare event that store fails, allow all attribute store attempts to go through and prune empty branches at the end altogether. + if (storeFailures) { + [self _pruneEmptyStoredAttributesBranches]; + MTR_LOG_ERROR("Store failed in -storeAttributeValues:forNodeID: failure count %lu", static_cast(storeFailures)); + } + }); +} + @end @implementation MTRCASESessionResumptionInfo @@ -895,6 +1073,7 @@ - (void)encodeWithCoder:(NSCoder *)coder [NSDictionary class], [NSString class], [MTRAttributePath class], + [MTRDeviceClusterData class], ]]; return sStorageClasses; } diff --git a/src/darwin/Framework/CHIP/MTRDevice_Internal.h b/src/darwin/Framework/CHIP/MTRDevice_Internal.h index bda6cceaace1c2..cb6aa46eb6f4ed 100644 --- a/src/darwin/Framework/CHIP/MTRDevice_Internal.h +++ b/src/darwin/Framework/CHIP/MTRDevice_Internal.h @@ -20,8 +20,7 @@ #import #import "MTRAsyncWorkQueue.h" - -#include +#import "MTRDefines_Internal.h" NS_ASSUME_NONNULL_BEGIN @@ -29,6 +28,15 @@ NS_ASSUME_NONNULL_BEGIN typedef void (^MTRDevicePerformAsyncBlock)(MTRBaseDevice * baseDevice); +/** + * Information about a cluster, currently is just data version + */ +MTR_TESTABLE +@interface MTRDeviceClusterData : NSObject +@property (nonatomic) NSNumber * dataVersion; +// TODO: add cluster attributes in this object, and remove direct attribute storage +@end + @interface MTRDevice () - (instancetype)initWithNodeID:(NSNumber *)nodeID controller:(MTRDeviceController *)controller; @@ -73,6 +81,10 @@ typedef void (^MTRDevicePerformAsyncBlock)(MTRBaseDevice * baseDevice); // reportChanges : if set to YES, attribute reports will also sent to the delegate if new values are different - (void)setAttributeValues:(NSArray *)attributeValues reportChanges:(BOOL)reportChanges; +// Method to insert cluster data +// Currently contains data version information +- (void)setClusterData:(NSDictionary *)clusterData; + @end #pragma mark - Utility for clamping numbers diff --git a/src/darwin/Framework/CHIPTests/MTRPerControllerStorageTests.m b/src/darwin/Framework/CHIPTests/MTRPerControllerStorageTests.m index efa53d9dabcefb..0fede084217997 100644 --- a/src/darwin/Framework/CHIPTests/MTRPerControllerStorageTests.m +++ b/src/darwin/Framework/CHIPTests/MTRPerControllerStorageTests.m @@ -20,6 +20,7 @@ #import #import "MTRDeviceTestDelegate.h" +#import "MTRDevice_Internal.h" #import "MTRErrorTestUtils.h" #import "MTRFabricInfoChecker.h" #import "MTRTestDeclarations.h" @@ -1082,6 +1083,19 @@ - (void)test008_TestDataStoreDirect [controller.controllerDataStore storeAttributeValues:testAttributes forNodeID:@(1002)]; [controller.controllerDataStore storeAttributeValues:testAttributes forNodeID:@(1003)]; + MTRDeviceClusterData * testClusterData1 = [[MTRDeviceClusterData alloc] init]; + testClusterData1.dataVersion = @(1); + MTRDeviceClusterData * testClusterData2 = [[MTRDeviceClusterData alloc] init]; + testClusterData2.dataVersion = @(2); + MTRDeviceClusterData * testClusterData3 = [[MTRDeviceClusterData alloc] init]; + testClusterData3.dataVersion = @(3); + NSDictionary * testClusterData = @{ + [MTRClusterPath clusterPathWithEndpointID:@(1) clusterID:@(1)] : testClusterData1, + [MTRClusterPath clusterPathWithEndpointID:@(1) clusterID:@(2)] : testClusterData2, + [MTRClusterPath clusterPathWithEndpointID:@(1) clusterID:@(3)] : testClusterData3, + }; + [controller.controllerDataStore storeClusterData:testClusterData forNodeID:@(1001)]; + // Check values are written and can be fetched NSArray * dataStoreValues = [controller.controllerDataStore getStoredAttributesForNodeID:@(1001)]; XCTAssertEqual(dataStoreValues.count, 9); @@ -1123,6 +1137,11 @@ - (void)test008_TestDataStoreDirect } } + NSDictionary * dataStoreClusterData = [controller.controllerDataStore getStoredClusterDataForNodeID:@(1001)]; + for (MTRClusterPath * path in testClusterData) { + XCTAssertEqualObjects(testClusterData[path].dataVersion, dataStoreClusterData[path].dataVersion); + } + [controller.controllerDataStore clearStoredAttributesForNodeID:@(1001)]; dataStoreValues = [controller.controllerDataStore getStoredAttributesForNodeID:@(1001)]; XCTAssertEqual(dataStoreValues.count, 0); diff --git a/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestDeclarations.h b/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestDeclarations.h index 02a711ee183470..68abb9a8fa319f 100644 --- a/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestDeclarations.h +++ b/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestDeclarations.h @@ -22,10 +22,13 @@ 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 NSArray *)getStoredAttributesForNodeID:(NSNumber *)nodeID; +- (nullable NSDictionary *)getStoredClusterDataForNodeID:(NSNumber *)nodeID; - (void)storeAttributeValues:(NSArray *)dataValues forNodeID:(NSNumber *)nodeID; +- (void)storeClusterData:(NSDictionary *)clusterData forNodeID:(NSNumber *)nodeID; - (void)clearStoredAttributesForNodeID:(NSNumber *)nodeID; - (void)clearAllStoredAttributes; - (void)unitTestPruneEmptyStoredAttributesBranches;