diff --git a/src/darwin/Framework/CHIP/MTRBaseDevice.h b/src/darwin/Framework/CHIP/MTRBaseDevice.h index 5eb00475dfdd8d..fe1592a45fc7b2 100644 --- a/src/darwin/Framework/CHIP/MTRBaseDevice.h +++ b/src/darwin/Framework/CHIP/MTRBaseDevice.h @@ -26,6 +26,9 @@ NS_ASSUME_NONNULL_BEGIN +typedef NSDictionary * MTRDeviceResponseValueDictionary; +typedef NSDictionary * MTRDeviceDataValueDictionary; + /** * Handler for read attribute response, write attribute response, invoke command response and reports. * @@ -96,7 +99,7 @@ NS_ASSUME_NONNULL_BEGIN * * MTRDataKey : Data-value NSDictionary object. */ -typedef void (^MTRDeviceResponseHandler)(NSArray *> * _Nullable values, NSError * _Nullable error); +typedef void (^MTRDeviceResponseHandler)(NSArray * _Nullable values, NSError * _Nullable error); /** * Handler for -subscribeWithQueue: attribute and event reports diff --git a/src/darwin/Framework/CHIP/MTRDevice.mm b/src/darwin/Framework/CHIP/MTRDevice.mm index 36ce2462c43000..9640a4a533005b 100644 --- a/src/darwin/Framework/CHIP/MTRDevice.mm +++ b/src/darwin/Framework/CHIP/MTRDevice.mm @@ -176,6 +176,7 @@ typedef NS_ENUM(NSUInteger, MTRDeviceWorkItemDuplicateTypeID) { @implementation MTRDeviceClusterData static NSString * const sDataVersionKey = @"dataVersion"; +static NSString * const sAttributesKey = @"attributes"; + (BOOL)supportsSecureCoding { @@ -184,7 +185,20 @@ + (BOOL)supportsSecureCoding - (NSString *)description { - return [NSString stringWithFormat:@"", _dataVersion]; + return [NSString stringWithFormat:@"", _dataVersion, static_cast(_attributes.count)]; +} + +- (nullable instancetype)initWithDataVersion:(NSNumber * _Nullable)dataVersion attributes:(NSDictionary * _Nullable)attributes +{ + self = [super init]; + if (self == nil) { + return nil; + } + + _dataVersion = [dataVersion copy]; + _attributes = [attributes copy]; + + return self; } - (nullable instancetype)initWithCoder:(NSCoder *)decoder @@ -200,12 +214,25 @@ - (nullable instancetype)initWithCoder:(NSCoder *)decoder 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]; } @end @@ -285,8 +312,11 @@ @implementation MTRDevice { NSUInteger _unitTestAttributesReportedSinceLastCheck; #endif BOOL _delegateDeviceCachePrimedCalled; + + // With MTRDeviceClusterData now able to hold attribute data, the plan is to move to using it + // as the read cache, should testing prove attribute storage by cluster is the better solution. NSMutableDictionary * _clusterData; - NSMutableDictionary * _clusterDataToPersist; + NSMutableSet * _clustersToPersist; } - (instancetype)initWithNodeID:(NSNumber *)nodeID controller:(MTRDeviceController *)controller @@ -836,6 +866,34 @@ - (void)_handleReportBegin } } +- (NSDictionary *)_attributesForCluster:(MTRClusterPath *)clusterPath +{ + os_unfair_lock_assert_owner(&self->_lock); + NSMutableDictionary * attributesToReturn = [NSMutableDictionary dictionary]; + for (MTRAttributePath * attributePath in _readCache) { + if ([attributePath.endpoint isEqualToNumber:clusterPath.endpoint] && [attributePath.cluster isEqualToNumber:clusterPath.cluster]) { + attributesToReturn[attributePath.attribute] = _readCache[attributePath]; + } + } + return attributesToReturn; +} + +- (NSDictionary *)_clusterDataForPaths:(NSSet *)clusterPaths +{ + os_unfair_lock_assert_owner(&self->_lock); + NSMutableDictionary * clusterDataToReturn = [NSMutableDictionary dictionary]; + for (MTRClusterPath * clusterPath in clusterPaths) { + NSNumber * dataVersion = _clusterData[clusterPath].dataVersion; + NSDictionary * attributes = [self _attributesForCluster:clusterPath]; + if (dataVersion || attributes) { + MTRDeviceClusterData * clusterData = [[MTRDeviceClusterData alloc] initWithDataVersion:dataVersion attributes:attributes]; + clusterDataToReturn[clusterPath] = clusterData; + } + } + + return clusterDataToReturn; +} + - (void)_handleReportEnd { std::lock_guard lock(_lock); @@ -844,10 +902,11 @@ - (void)_handleReportEnd _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; + if (dataStoreExists && _clustersToPersist.count) { + MTR_LOG_DEFAULT("%@ Storing cluster information (data version) count: %lu", self, static_cast(_clustersToPersist.count)); + NSDictionary * clusterData = [self _clusterDataForPaths:_clustersToPersist]; + [_deviceController.controllerDataStore storeClusterData:clusterData forNodeID:_nodeID]; + _clustersToPersist = nil; } // For unit testing only @@ -1939,13 +1998,28 @@ - (void)_performScheduledExpirationCheck - (BOOL)_attributeDataValue:(NSDictionary *)one isEqualToDataValue:(NSDictionary *)theOther { - // Attribute data-value dictionaries are equal if type and value are equal + // Sanity check for nil cases + if (!one && !theOther) { + MTR_LOG_ERROR("%@ attribute data-value comparison does not expect comparing two nil dictionaries", self); + return YES; + } + if (!one || !theOther) { + MTR_LOG_ERROR("%@ attribute data-value comparison does not expect a dictionary to be nil: %@ %@", self, one, theOther); + return NO; + } + + // Attribute data-value dictionaries are equal if type and value are equal, and specifically, this should return true if values are both nil return [one[MTRTypeKey] isEqual:theOther[MTRTypeKey]] && ((one[MTRValueKey] == theOther[MTRValueKey]) || [one[MTRValueKey] isEqual:theOther[MTRValueKey]]); } // 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 { @@ -1954,9 +2028,12 @@ - (NSDictionary *)_dataValueWithoutDataVersion:(NSDictionary *)attributeValue; } // Update cluster data version and also note the change, so at onReportEnd it can be persisted -- (void)_updateDataVersion:(NSNumber *)dataVersion forClusterPath:(MTRClusterPath *)clusterPath +- (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 = _clusterData[clusterPath]; if (!clusterData) { clusterData = [[MTRDeviceClusterData alloc] init]; @@ -1969,17 +2046,25 @@ - (void)_updateDataVersion:(NSNumber *)dataVersion forClusterPath:(MTRClusterPat if (dataVersionChanged) { clusterData.dataVersion = dataVersion; - // Set up for persisting if there is data store + // Mark cluster path as needing persistence if needed BOOL dataStoreExists = _deviceController.controllerDataStore != nil; if (dataStoreExists) { - if (!_clusterDataToPersist) { - _clusterDataToPersist = [NSMutableDictionary dictionary]; - } - _clusterDataToPersist[clusterPath] = clusterData; + [self _noteChangeForClusterPath:clusterPath]; } } } +// Assuming data store exists, note that the cluster should be persisted at onReportEnd +- (void)_noteChangeForClusterPath:(MTRClusterPath *)clusterPath +{ + os_unfair_lock_assert_owner(&self->_lock); + + if (!_clustersToPersist) { + _clustersToPersist = [NSMutableSet set]; + } + [_clustersToPersist addObject:clusterPath]; +} + // assume lock is held - (NSArray *)_getAttributesToReportWithReportedValues:(NSArray *> *)reportedAttributeValues { @@ -1988,10 +2073,12 @@ - (NSArray *)_getAttributesToReportWithReportedValues:(NSArray * attributeResponseValue in reportedAttributeValues) { MTRAttributePath * attributePath = attributeResponseValue[MTRAttributePathKey]; NSDictionary * attributeDataValue = attributeResponseValue[MTRDataKey]; @@ -2023,9 +2110,9 @@ - (NSArray *)_getAttributesToReportWithReportedValues:(NSArray *)attributeValues reportChanges:(BOOL)reportChanges +- (void)_setAttributeValues:(NSArray *)attributeValues reportChanges:(BOOL)reportChanges { - MTR_LOG_INFO("%@ setAttributeValues count: %lu reportChanges: %d", self, static_cast(attributeValues.count), reportChanges); - std::lock_guard lock(_lock); + os_unfair_lock_assert_owner(&self->_lock); + + if (!attributeValues.count) { + return; + } if (reportChanges) { [self _reportAttributes:[self _getAttributesToReportWithReportedValues:attributeValues]]; @@ -2134,8 +2230,42 @@ - (void)setAttributeValues:(NSArray *)attributeValues reportChan } } +- (void)setAttributeValues:(NSArray *)attributeValues reportChanges:(BOOL)reportChanges +{ + MTR_LOG_INFO("%@ setAttributeValues count: %lu reportChanges: %d", self, static_cast(attributeValues.count), reportChanges); + std::lock_guard lock(_lock); + [self _setAttributeValues:attributeValues reportChanges:reportChanges]; +} + - (void)setClusterData:(NSDictionary *)clusterData { + MTR_LOG_INFO("%@ setClusterData count: %lu", self, static_cast(clusterData.count)); + if (!clusterData.count) { + return; + } + + std::lock_guard lock(_lock); + +#if MTRDEVICE_ATTRIBUTE_CACHE_STORE_ATTRIBUTES_BY_CLUSTER + // For each cluster, extract and create the attribute response-value for the read cache + // TODO: consider some optimization in how the read cache is structured so there's fewer conversions from this format to what's in the cache + for (MTRClusterPath * clusterPath in clusterData) { + MTRDeviceClusterData * data = clusterData[clusterPath]; + // Build and set attributes one cluster at a time to avoid creating a ton of temporary objects at a time + @autoreleasepool { + NSMutableArray * attributeValues = [NSMutableArray array]; + for (NSNumber * attributeID in data.attributes) { + MTRAttributePath * attributePath = [MTRAttributePath attributePathWithEndpointID:clusterPath.endpoint clusterID:clusterPath.cluster attributeID:attributeID]; + NSDictionary * responseValue = @{ MTRAttributePathKey : attributePath, MTRDataKey : data.attributes[attributeID] }; + [attributeValues addObject:responseValue]; + } + if (attributeValues.count) { + [self _setAttributeValues:attributeValues reportChanges:NO]; + } + } + } +#endif + [_clusterData addEntriesFromDictionary:clusterData]; } diff --git a/src/darwin/Framework/CHIP/MTRDeviceController.mm b/src/darwin/Framework/CHIP/MTRDeviceController.mm index 1e52592f94d9ce..95c21e816ecb4a 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceController.mm @@ -926,12 +926,15 @@ - (MTRDevice *)deviceForNodeID:(NSNumber *)nodeID _nodeIDToDeviceMap[nodeID] = deviceToReturn; } +#if !MTRDEVICE_ATTRIBUTE_CACHE_STORE_ATTRIBUTES_BY_CLUSTER // Load persisted attributes if they exist. NSArray * attributesFromCache = [_controllerDataStore getStoredAttributesForNodeID:nodeID]; MTR_LOG_INFO("Loaded %lu attributes from storage for %@", static_cast(attributesFromCache.count), deviceToReturn); if (attributesFromCache.count) { [deviceToReturn setAttributeValues:attributesFromCache reportChanges:NO]; } +#endif + // Load persisted cluster data if they exist. NSDictionary * clusterData = [_controllerDataStore getStoredClusterDataForNodeID:nodeID]; MTR_LOG_INFO("Loaded %lu cluster data from storage for %@", static_cast(clusterData.count), deviceToReturn); if (clusterData.count) { diff --git a/src/darwin/Framework/CHIP/MTRDeviceControllerDataStore.h b/src/darwin/Framework/CHIP/MTRDeviceControllerDataStore.h index 90007dbb2df861..c49bc897f6b4c0 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceControllerDataStore.h +++ b/src/darwin/Framework/CHIP/MTRDeviceControllerDataStore.h @@ -68,8 +68,8 @@ 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; +- (nullable NSDictionary *)getStoredClusterDataForNodeID:(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 38a089bad0f92e..49a653e6e73bbe 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceControllerDataStore.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceControllerDataStore.mm @@ -371,16 +371,31 @@ - (NSString *)_endpointIndexKeyForNodeID:(NSNumber *)nodeID - (nullable NSArray *)_fetchEndpointIndexForNodeID:(NSNumber *)nodeID { + if (!nodeID) { + MTR_LOG_ERROR("%s: unexpected nil input", __func__); + return nil; + } + return [self _fetchAttributeCacheValueForKey:[self _endpointIndexKeyForNodeID:nodeID] expectedClass:[NSArray class]]; } - (BOOL)_storeEndpointIndex:(NSArray *)endpointIndex forNodeID:(NSNumber *)nodeID { + if (!nodeID) { + MTR_LOG_ERROR("%s: unexpected nil input", __func__); + return NO; + } + return [self _storeAttributeCacheValue:endpointIndex forKey:[self _endpointIndexKeyForNodeID:nodeID]]; } - (BOOL)_deleteEndpointIndexForNodeID:(NSNumber *)nodeID { + if (!nodeID) { + MTR_LOG_ERROR("%s: unexpected nil input", __func__); + return NO; + } + return [self _removeAttributeCacheValueForKey:[self _endpointIndexKeyForNodeID:nodeID]]; } @@ -393,16 +408,31 @@ - (NSString *)_clusterIndexKeyForNodeID:(NSNumber *)nodeID endpointID:(NSNumber - (nullable NSArray *)_fetchClusterIndexForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID { + if (!nodeID || !endpointID) { + MTR_LOG_ERROR("%s: unexpected nil input", __func__); + return nil; + } + return [self _fetchAttributeCacheValueForKey:[self _clusterIndexKeyForNodeID:nodeID endpointID:endpointID] expectedClass:[NSArray class]]; } - (BOOL)_storeClusterIndex:(NSArray *)clusterIndex forNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID { + if (!nodeID || !endpointID) { + MTR_LOG_ERROR("%s: unexpected nil input", __func__); + return NO; + } + return [self _storeAttributeCacheValue:clusterIndex forKey:[self _clusterIndexKeyForNodeID:nodeID endpointID:endpointID]]; } - (BOOL)_deleteClusterIndexForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID { + if (!nodeID || !endpointID) { + MTR_LOG_ERROR("%s: unexpected nil input", __func__); + return NO; + } + return [self _removeAttributeCacheValueForKey:[self _clusterIndexKeyForNodeID:nodeID endpointID:endpointID]]; } @@ -415,16 +445,31 @@ - (NSString *)_clusterDataKeyForNodeID:(NSNumber *)nodeID endpointID:(NSNumber * - (nullable MTRDeviceClusterData *)_fetchClusterDataForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID { + if (!nodeID || !endpointID || !clusterID) { + MTR_LOG_ERROR("%s: unexpected nil input", __func__); + return nil; + } + 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 { + if (!nodeID || !endpointID || !clusterID || !clusterData) { + MTR_LOG_ERROR("%s: unexpected nil input", __func__); + return NO; + } + return [self _storeAttributeCacheValue:clusterData forKey:[self _clusterDataKeyForNodeID:nodeID endpointID:endpointID clusterID:clusterID]]; } - (BOOL)_deleteClusterDataForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID { + if (!nodeID || !endpointID || !clusterID) { + MTR_LOG_ERROR("%s: unexpected nil input", __func__); + return NO; + } + return [self _removeAttributeCacheValueForKey:[self _clusterDataKeyForNodeID:nodeID endpointID:endpointID clusterID:clusterID]]; } @@ -510,7 +555,7 @@ - (BOOL)_deleteAttributeValueForNodeID:(NSNumber *)nodeID endpointID:(NSNumber * #endif for (NSNumber * endpointID in endpointIndex) { - // Fetch endpoint index + // Fetch cluster index NSArray * clusterIndex = [self _fetchClusterIndexForNodeID:nodeID endpointID:endpointID]; #if ATTRIBUTE_CACHE_VERBOSE_LOGGING @@ -518,7 +563,7 @@ - (BOOL)_deleteAttributeValueForNodeID:(NSNumber *)nodeID endpointID:(NSNumber * #endif for (NSNumber * clusterID in clusterIndex) { - // Fetch endpoint index + // Fetch attribute index NSArray * attributeIndex = [self _fetchAttributeIndexForNodeID:nodeID endpointID:endpointID clusterID:clusterID]; #if ATTRIBUTE_CACHE_VERBOSE_LOGGING @@ -576,12 +621,12 @@ - (void)_pruneEmptyStoredAttributesBranches NSMutableArray * endpointIndexCopy = [endpointIndex mutableCopy]; for (NSNumber * endpointID in endpointIndex) { - // Fetch endpoint index + // Fetch cluster index NSArray * clusterIndex = [self _fetchClusterIndexForNodeID:nodeID endpointID:endpointID]; NSMutableArray * clusterIndexCopy = [clusterIndex mutableCopy]; for (NSNumber * clusterID in clusterIndex) { - // Fetch endpoint index + // Fetch attribute index NSArray * attributeIndex = [self _fetchAttributeIndexForNodeID:nodeID endpointID:endpointID clusterID:clusterID]; NSMutableArray * attributeIndexCopy = [attributeIndex mutableCopy]; @@ -857,6 +902,11 @@ - (void)clearAllStoredAttributes - (nullable NSDictionary *)getStoredClusterDataForNodeID:(NSNumber *)nodeID { + if (!nodeID) { + MTR_LOG_ERROR("%s: unexpected nil input", __func__); + return nil; + } + __block NSMutableDictionary * clusterDataToReturn = nil; dispatch_sync(_storageDelegateQueue, ^{ // Fetch node index @@ -887,7 +937,7 @@ - (void)clearAllStoredAttributes #endif for (NSNumber * endpointID in endpointIndex) { - // Fetch endpoint index + // Fetch cluster index NSArray * clusterIndex = [self _fetchClusterIndexForNodeID:nodeID endpointID:endpointID]; #if ATTRIBUTE_CACHE_VERBOSE_LOGGING @@ -920,6 +970,16 @@ - (void)clearAllStoredAttributes - (void)storeClusterData:(NSDictionary *)clusterData forNodeID:(NSNumber *)nodeID { + if (!nodeID) { + MTR_LOG_ERROR("%s: unexpected nil input", __func__); + return; + } + + if (!clusterData.count) { + MTR_LOG_ERROR("%s: nothing to store", __func__); + return; + } + dispatch_async(_storageDelegateQueue, ^{ NSUInteger storeFailures = 0; diff --git a/src/darwin/Framework/CHIP/MTRDevice_Internal.h b/src/darwin/Framework/CHIP/MTRDevice_Internal.h index cb6aa46eb6f4ed..5d82847f4cbd00 100644 --- a/src/darwin/Framework/CHIP/MTRDevice_Internal.h +++ b/src/darwin/Framework/CHIP/MTRDevice_Internal.h @@ -28,13 +28,16 @@ NS_ASSUME_NONNULL_BEGIN typedef void (^MTRDevicePerformAsyncBlock)(MTRBaseDevice * baseDevice); +// Whether to store attributes by cluster instead of as individual entries for each attribute +#define MTRDEVICE_ATTRIBUTE_CACHE_STORE_ATTRIBUTES_BY_CLUSTER 1 + /** * Information about a cluster, currently is just data version */ MTR_TESTABLE -@interface MTRDeviceClusterData : NSObject +@interface MTRDeviceClusterData : NSObject @property (nonatomic) NSNumber * dataVersion; -// TODO: add cluster attributes in this object, and remove direct attribute storage +@property (nonatomic) NSDictionary * attributes; // attributeID => data-value dictionary @end @interface MTRDevice () diff --git a/src/darwin/Framework/CHIPTests/MTRDeviceTests.m b/src/darwin/Framework/CHIPTests/MTRDeviceTests.m index e5539f4f75d993..1d71c7b00ea4ce 100644 --- a/src/darwin/Framework/CHIPTests/MTRDeviceTests.m +++ b/src/darwin/Framework/CHIPTests/MTRDeviceTests.m @@ -27,6 +27,7 @@ #import "MTRCommandPayloadExtensions_Internal.h" #import "MTRDeviceControllerLocalTestStorage.h" #import "MTRDeviceTestDelegate.h" +#import "MTRDevice_Internal.h" #import "MTRErrorTestUtils.h" #import "MTRTestDeclarations.h" #import "MTRTestKeys.h" @@ -2870,8 +2871,13 @@ - (void)test031_MTRDeviceAttributeCacheLocalTestStorage NSUInteger attributesReportedWithFirstSubscription = [device unitTestAttributesReportedSinceLastCheck]; +#if MTRDEVICE_ATTRIBUTE_CACHE_STORE_ATTRIBUTES_BY_CLUSTER + NSDictionary * dataStoreClusterDataAfterFirstSubscription = [sController.controllerDataStore getStoredClusterDataForNodeID:@(kDeviceId)]; + XCTAssertTrue(dataStoreClusterDataAfterFirstSubscription.count > 0); +#else NSArray * dataStoreValuesAfterFirstSubscription = [sController.controllerDataStore getStoredAttributesForNodeID:@(kDeviceId)]; XCTAssertTrue(dataStoreValuesAfterFirstSubscription.count > 0); +#endif // Now remove device, resubscribe, and see that it succeeds [sController removeDevice:device]; diff --git a/src/darwin/Framework/CHIPTests/MTRPerControllerStorageTests.m b/src/darwin/Framework/CHIPTests/MTRPerControllerStorageTests.m index 0fede084217997..fce1ff18b03cf1 100644 --- a/src/darwin/Framework/CHIPTests/MTRPerControllerStorageTests.m +++ b/src/darwin/Framework/CHIPTests/MTRPerControllerStorageTests.m @@ -1249,7 +1249,21 @@ - (void)test009_TestDataStoreMTRDevice [self waitForExpectations:@[ subscriptionExpectation ] timeout:60]; + NSUInteger dataStoreValuesCount = 0; +#if MTRDEVICE_ATTRIBUTE_CACHE_STORE_ATTRIBUTES_BY_CLUSTER + NSDictionary * dataStoreClusterData = [controller.controllerDataStore getStoredClusterDataForNodeID:deviceID]; + for (MTRClusterPath * path in dataStoreClusterData) { + MTRDeviceClusterData * data = dataStoreClusterData[path]; + for (NSNumber * attributeID in data.attributes) { + dataStoreValuesCount++; + NSDictionary * dataValue = data.attributes[attributeID]; + NSDictionary * dataValueFromMTRDevice = [device readAttributeWithEndpointID:path.endpoint clusterID:path.cluster attributeID:attributeID params:nil]; + XCTAssertTrue([device _attributeDataValue:dataValue isEqualToDataValue:dataValueFromMTRDevice]); + } + } +#else NSArray * dataStoreValues = [controller.controllerDataStore getStoredAttributesForNodeID:deviceID]; + dataStoreValuesCount = dataStoreValues.count; // Verify all values are stored into storage for (NSDictionary * responseValue in dataStoreValues) { @@ -1261,6 +1275,7 @@ - (void)test009_TestDataStoreMTRDevice NSDictionary * dataValueFromMTRDevice = [device readAttributeWithEndpointID:path.endpoint clusterID:path.cluster attributeID:path.attribute params:nil]; XCTAssertTrue([device _attributeDataValue:dataValue isEqualToDataValue:dataValueFromMTRDevice]); } +#endif // Now force the removal of the object from controller to test reloading read cache from storage [controller removeDevice:device]; @@ -1268,6 +1283,18 @@ - (void)test009_TestDataStoreMTRDevice // Verify the new device is initialized with the same values __auto_type * newDevice = [MTRDevice deviceWithNodeID:deviceID controller:controller]; NSUInteger storedAttributeDifferFromMTRDeviceCount = 0; +#if MTRDEVICE_ATTRIBUTE_CACHE_STORE_ATTRIBUTES_BY_CLUSTER + for (MTRClusterPath * path in dataStoreClusterData) { + MTRDeviceClusterData * data = dataStoreClusterData[path]; + for (NSNumber * attributeID in data.attributes) { + NSDictionary * dataValue = data.attributes[attributeID]; + NSDictionary * dataValueFromMTRDevice = [newDevice readAttributeWithEndpointID:path.endpoint clusterID:path.cluster attributeID:attributeID params:nil]; + if (![newDevice _attributeDataValue:dataValue isEqualToDataValue:dataValueFromMTRDevice]) { + storedAttributeDifferFromMTRDeviceCount++; + } + } + } +#else for (NSDictionary * responseValue in dataStoreValues) { MTRAttributePath * path = responseValue[MTRAttributePathKey]; XCTAssertNotNil(path); @@ -1279,10 +1306,11 @@ - (void)test009_TestDataStoreMTRDevice storedAttributeDifferFromMTRDeviceCount++; } } +#endif // Only test that 90% of attributes are the same because there are some changing attributes each time (UTC time, for example) // * With all-clusters-app as of 2024-02-10, about 1.476% of attributes change. - double storedAttributeDifferFromMTRDevicePercentage = storedAttributeDifferFromMTRDeviceCount * 100.0 / dataStoreValues.count; + double storedAttributeDifferFromMTRDevicePercentage = storedAttributeDifferFromMTRDeviceCount * 100.0 / dataStoreValuesCount; XCTAssertTrue(storedAttributeDifferFromMTRDevicePercentage < 10.0); // Now @@ -1303,7 +1331,7 @@ - (void)test009_TestDataStoreMTRDevice // 2) Some attributes do change on resubscribe // * With all-clusts-app as of 2024-02-10, out of 1287 persisted attributes, still 450 attributes were reported with filter // And so conservatively, assert that data version filters save at least 300 entries. - NSUInteger storedAttributeCountDifferenceFromMTRDeviceReport = dataStoreValues.count - [device unitTestAttributesReportedSinceLastCheck]; + NSUInteger storedAttributeCountDifferenceFromMTRDeviceReport = dataStoreValuesCount - [device unitTestAttributesReportedSinceLastCheck]; XCTAssertTrue(storedAttributeCountDifferenceFromMTRDeviceReport > 300); // Reset our commissionee.