Switch MTRDevice to always using by-cluster persistent attribute storage. (#33096)
MTRDEVICE_ATTRIBUTE_CACHE_STORE_ATTRIBUTES_BY_CLUSTER was already defaulted to
1; this just removes the codepaths when this is set to 0.
Cleans up some of the controller data store bits that then become unused, and
improves test coverage of this codepath.
Fixes formatting of the endpoint id in _clusterDataKeyForNodeID.
diff --git a/src/darwin/Framework/CHIP/MTRDevice.mm b/src/darwin/Framework/CHIP/MTRDevice.mm
index d171c39..374b2e8 100644
--- a/src/darwin/Framework/CHIP/MTRDevice.mm
+++ b/src/darwin/Framework/CHIP/MTRDevice.mm
@@ -1040,9 +1040,7 @@
for (MTRClusterPath * clusterPath in clusterPaths) {
NSNumber * dataVersion = _clusterData[clusterPath].dataVersion;
NSDictionary<NSNumber *, MTRDeviceDataValueDictionary> * attributes = nil;
-#if MTRDEVICE_ATTRIBUTE_CACHE_STORE_ATTRIBUTES_BY_CLUSTER
attributes = [self _attributesForCluster:clusterPath];
-#endif
if (dataVersion || attributes) {
MTRDeviceClusterData * clusterData = [[MTRDeviceClusterData alloc] initWithDataVersion:dataVersion attributes:attributes];
clusterDataToReturn[clusterPath] = clusterData;
@@ -2340,12 +2338,6 @@
NSMutableArray * attributesToReport = [NSMutableArray array];
NSMutableArray * attributePathsToReport = [NSMutableArray array];
BOOL dataStoreExists = _deviceController.controllerDataStore != nil;
-#if !MTRDEVICE_ATTRIBUTE_CACHE_STORE_ATTRIBUTES_BY_CLUSTER
- NSMutableArray * attributesToPersist;
- if (dataStoreExists) {
- attributesToPersist = [NSMutableArray array];
- }
-#endif
for (NSDictionary<NSString *, id> * attributeResponseValue in reportedAttributeValues) {
MTRAttributePath * attributePath = attributeResponseValue[MTRAttributePathKey];
NSDictionary * attributeDataValue = attributeResponseValue[MTRDataKey];
@@ -2388,20 +2380,7 @@
BOOL readCacheValueChanged = ![self _attributeDataValue:attributeDataValue isEqualToDataValue:_readCache[attributePath]];
// Check if attribute needs to be persisted - compare only to read cache and disregard expected values
if (dataStoreExists && readCacheValueChanged) {
-#if MTRDEVICE_ATTRIBUTE_CACHE_STORE_ATTRIBUTES_BY_CLUSTER
[self _noteChangeForClusterPath:clusterPath];
-#else
- NSDictionary * attributeResponseValueToPersist;
- if (dataVersion) {
- // Remove data version from what we cache in memory and storage
- NSMutableDictionary * attributeResponseValueCopy = [attributeResponseValue mutableCopy];
- attributeResponseValueCopy[MTRDataKey] = attributeDataValue;
- attributeResponseValueToPersist = attributeResponseValueCopy;
- } else {
- attributeResponseValueToPersist = attributeResponseValue;
- }
- [attributesToPersist addObject:attributeResponseValueToPersist];
-#endif
}
NSArray * expectedValue = _expectedValueCache[attributePath];
@@ -2473,12 +2452,6 @@
MTR_LOG_INFO("%@ report from reported values %@", self, attributePathsToReport);
-#if !MTRDEVICE_ATTRIBUTE_CACHE_STORE_ATTRIBUTES_BY_CLUSTER
- if (dataStoreExists && attributesToPersist.count) {
- [_deviceController.controllerDataStore storeAttributeValues:attributesToPersist forNodeID:_nodeID];
- }
-#endif
-
return attributesToReport;
}
@@ -2530,7 +2503,6 @@
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) {
@@ -2548,7 +2520,6 @@
}
}
}
-#endif
[_clusterData addEntriesFromDictionary:clusterData];
}
diff --git a/src/darwin/Framework/CHIP/MTRDeviceController.mm b/src/darwin/Framework/CHIP/MTRDeviceController.mm
index 6150b1c..4dfa229 100644
--- a/src/darwin/Framework/CHIP/MTRDeviceController.mm
+++ b/src/darwin/Framework/CHIP/MTRDeviceController.mm
@@ -948,14 +948,6 @@
[deviceToReturn setClusterData:prefetchedClusterData];
}
} else {
-#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<unsigned long>(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<unsigned long>(clusterData.count), deviceToReturn);
diff --git a/src/darwin/Framework/CHIP/MTRDeviceControllerDataStore.h b/src/darwin/Framework/CHIP/MTRDeviceControllerDataStore.h
index bc3b8f3..91a6c0e 100644
--- a/src/darwin/Framework/CHIP/MTRDeviceControllerDataStore.h
+++ b/src/darwin/Framework/CHIP/MTRDeviceControllerDataStore.h
@@ -72,12 +72,10 @@
/**
* 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<NSDictionary *> *)getStoredAttributesForNodeID:(NSNumber *)nodeID;
-- (void)storeAttributeValues:(NSArray<NSDictionary *> *)dataValues forNodeID:(NSNumber *)nodeID;
- (nullable NSDictionary<MTRClusterPath *, MTRDeviceClusterData *> *)getStoredClusterDataForNodeID:(NSNumber *)nodeID;
- (void)storeClusterData:(NSDictionary<MTRClusterPath *, MTRDeviceClusterData *> *)clusterData forNodeID:(NSNumber *)nodeID;
-- (void)clearStoredAttributesForNodeID:(NSNumber *)nodeID;
-- (void)clearAllStoredAttributes;
+- (void)clearStoredClusterDataForNodeID:(NSNumber *)nodeID;
+- (void)clearAllStoredClusterData;
@end
diff --git a/src/darwin/Framework/CHIP/MTRDeviceControllerDataStore.mm b/src/darwin/Framework/CHIP/MTRDeviceControllerDataStore.mm
index d6c7e8c..cac425c 100644
--- a/src/darwin/Framework/CHIP/MTRDeviceControllerDataStore.mm
+++ b/src/darwin/Framework/CHIP/MTRDeviceControllerDataStore.mm
@@ -306,20 +306,14 @@
* key: "attrCacheNodeIndex"
* value: list of nodeIDs
* EndpointID index
- * key: "attrCacheEndpointIndex:<nodeID>:endpointID"
+ * key: "attrCacheEndpointIndex:<nodeID>"
* value: list of endpoint IDs
* ClusterID index
- * key: "<nodeID+endpointID> clusters"
+ * key: "attrCacheClusterIndex:<nodeID>:<endpointID>"
* value: list of cluster IDs
- * AttributeID index
- * key: "<nodeID+endpointID+clusterID> attributes"
- * value: list of attribute IDs
- * Attribute data entry:
- * key: "<nodeID+endpointID+clusterID+attributeID> attribute data"
- * value: serialized dictionary of attribute data
- *
- * Attribute data dictionary
- * Additional value "serial number"
+ * Cluster data entry:
+ * key: "attrCacheClusterData:<nodeID>:<endpointID>:<clusterID>"
+ * value: MTRDeviceClusterData
*/
- (id)_fetchAttributeCacheValueForKey:(NSString *)key expectedClass:(Class)expectedClass;
@@ -371,16 +365,19 @@
- (nullable NSArray<NSNumber *> *)_fetchNodeIndex
{
+ dispatch_assert_queue(_storageDelegateQueue);
return [self _fetchAttributeCacheValueForKey:sAttributeCacheNodeIndexKey expectedClass:[NSArray class]];
}
- (BOOL)_storeNodeIndex:(NSArray<NSNumber *> *)nodeIndex
{
+ dispatch_assert_queue(_storageDelegateQueue);
return [self _storeAttributeCacheValue:nodeIndex forKey:sAttributeCacheNodeIndexKey];
}
- (BOOL)_deleteNodeIndex
{
+ dispatch_assert_queue(_storageDelegateQueue);
return [self _removeAttributeCacheValueForKey:sAttributeCacheNodeIndexKey];
}
@@ -393,6 +390,8 @@
- (nullable NSArray<NSNumber *> *)_fetchEndpointIndexForNodeID:(NSNumber *)nodeID
{
+ dispatch_assert_queue(_storageDelegateQueue);
+
if (!nodeID) {
MTR_LOG_ERROR("%s: unexpected nil input", __func__);
return nil;
@@ -403,6 +402,8 @@
- (BOOL)_storeEndpointIndex:(NSArray<NSNumber *> *)endpointIndex forNodeID:(NSNumber *)nodeID
{
+ dispatch_assert_queue(_storageDelegateQueue);
+
if (!nodeID) {
MTR_LOG_ERROR("%s: unexpected nil input", __func__);
return NO;
@@ -413,6 +414,8 @@
- (BOOL)_deleteEndpointIndexForNodeID:(NSNumber *)nodeID
{
+ dispatch_assert_queue(_storageDelegateQueue);
+
if (!nodeID) {
MTR_LOG_ERROR("%s: unexpected nil input", __func__);
return NO;
@@ -430,6 +433,8 @@
- (nullable NSArray<NSNumber *> *)_fetchClusterIndexForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID
{
+ dispatch_assert_queue(_storageDelegateQueue);
+
if (!nodeID || !endpointID) {
MTR_LOG_ERROR("%s: unexpected nil input", __func__);
return nil;
@@ -440,6 +445,8 @@
- (BOOL)_storeClusterIndex:(NSArray<NSNumber *> *)clusterIndex forNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID
{
+ dispatch_assert_queue(_storageDelegateQueue);
+
if (!nodeID || !endpointID) {
MTR_LOG_ERROR("%s: unexpected nil input", __func__);
return NO;
@@ -450,6 +457,8 @@
- (BOOL)_deleteClusterIndexForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID
{
+ dispatch_assert_queue(_storageDelegateQueue);
+
if (!nodeID || !endpointID) {
MTR_LOG_ERROR("%s: unexpected nil input", __func__);
return NO;
@@ -462,11 +471,13 @@
- (NSString *)_clusterDataKeyForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID
{
- return [sAttributeCacheClusterDataKeyPrefix stringByAppendingFormat:@":0x%016llX:%0x04X:0x%08lX", nodeID.unsignedLongLongValue, endpointID.unsignedShortValue, clusterID.unsignedLongValue];
+ return [sAttributeCacheClusterDataKeyPrefix stringByAppendingFormat:@":0x%016llX:0x%04X:0x%08lX", nodeID.unsignedLongLongValue, endpointID.unsignedShortValue, clusterID.unsignedLongValue];
}
- (nullable MTRDeviceClusterData *)_fetchClusterDataForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID
{
+ dispatch_assert_queue(_storageDelegateQueue);
+
if (!nodeID || !endpointID || !clusterID) {
MTR_LOG_ERROR("%s: unexpected nil input", __func__);
return nil;
@@ -477,6 +488,8 @@
- (BOOL)_storeClusterData:(MTRDeviceClusterData *)clusterData forNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID
{
+ dispatch_assert_queue(_storageDelegateQueue);
+
if (!nodeID || !endpointID || !clusterID || !clusterData) {
MTR_LOG_ERROR("%s: unexpected nil input", __func__);
return NO;
@@ -487,6 +500,8 @@
- (BOOL)_deleteClusterDataForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID
{
+ dispatch_assert_queue(_storageDelegateQueue);
+
if (!nodeID || !endpointID || !clusterID) {
MTR_LOG_ERROR("%s: unexpected nil input", __func__);
return NO;
@@ -495,139 +510,22 @@
return [self _removeAttributeCacheValueForKey:[self _clusterDataKeyForNodeID:nodeID endpointID:endpointID clusterID:clusterID]];
}
-static NSString * sAttributeCacheAttributeIndexKeyPrefix = @"attrCacheAttributeIndex";
-
-- (NSString *)_attributeIndexKeyForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID
-{
- return [sAttributeCacheAttributeIndexKeyPrefix stringByAppendingFormat:@":0x%016llX:0x%04X:0x%08lX", nodeID.unsignedLongLongValue, endpointID.unsignedShortValue, clusterID.unsignedLongValue];
-}
-
-- (nullable NSArray<NSNumber *> *)_fetchAttributeIndexForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID
-{
- return [self _fetchAttributeCacheValueForKey:[self _attributeIndexKeyForNodeID:nodeID endpointID:endpointID clusterID:clusterID] expectedClass:[NSArray class]];
-}
-
-- (BOOL)_storeAttributeIndex:(NSArray<NSNumber *> *)attributeIndex forNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID
-{
- return [self _storeAttributeCacheValue:attributeIndex forKey:[self _attributeIndexKeyForNodeID:nodeID endpointID:endpointID clusterID:clusterID]];
-}
-
-- (BOOL)_deleteAttributeIndexForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID
-{
- return [self _removeAttributeCacheValueForKey:[self _attributeIndexKeyForNodeID:nodeID endpointID:endpointID clusterID:clusterID]];
-}
-
-static NSString * sAttributeCacheAttributeValueKeyPrefix = @"attrCacheAttributeValue";
-
-- (NSString *)_attributeValueKeyForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID attributeID:(NSNumber *)attributeID
-{
- return [sAttributeCacheAttributeValueKeyPrefix stringByAppendingFormat:@":0x%016llX:0x%04X:0x%08lX:0x%08lX", nodeID.unsignedLongLongValue, endpointID.unsignedShortValue, clusterID.unsignedLongValue, attributeID.unsignedLongValue];
-}
-
-- (nullable NSDictionary *)_fetchAttributeValueForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID attributeID:(NSNumber *)attributeID
-{
- return [self _fetchAttributeCacheValueForKey:[self _attributeValueKeyForNodeID:nodeID endpointID:endpointID clusterID:clusterID attributeID:attributeID] expectedClass:[NSDictionary class]];
-}
-
-- (BOOL)_storeAttributeValue:(NSDictionary *)value forNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID attributeID:(NSNumber *)attributeID
-{
- return [self _storeAttributeCacheValue:value forKey:[self _attributeValueKeyForNodeID:nodeID endpointID:endpointID clusterID:clusterID attributeID:attributeID]];
-}
-
-- (BOOL)_deleteAttributeValueForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID attributeID:(NSNumber *)attributeID
-{
- return [self _removeAttributeCacheValueForKey:[self _attributeValueKeyForNodeID:nodeID endpointID:endpointID clusterID:clusterID attributeID:attributeID]];
-}
-
#pragma - Attribute Cache management
#ifndef ATTRIBUTE_CACHE_VERBOSE_LOGGING
#define ATTRIBUTE_CACHE_VERBOSE_LOGGING 0
#endif
-- (nullable NSArray<NSDictionary *> *)getStoredAttributesForNodeID:(NSNumber *)nodeID
-{
- __block NSMutableArray * attributesToReturn = nil;
- dispatch_sync(_storageDelegateQueue, ^{
- // Fetch node index
- NSArray<NSNumber *> * nodeIndex = [self _fetchNodeIndex];
-
-#if ATTRIBUTE_CACHE_VERBOSE_LOGGING
- MTR_LOG_INFO("Fetch got %lu values for nodeIndex", static_cast<unsigned long>(nodeIndex.count));
-#endif
-
- if (![nodeIndex containsObject:nodeID]) {
- // Sanity check and delete if nodeID exists in index
- NSArray<NSNumber *> * 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);
- attributesToReturn = nil;
- return;
- }
-
- // Fetch endpoint index
- NSArray<NSNumber *> * endpointIndex = [self _fetchEndpointIndexForNodeID:nodeID];
-
-#if ATTRIBUTE_CACHE_VERBOSE_LOGGING
- MTR_LOG_INFO("Fetch got %lu values for endpointIndex @ node 0x%016llX", static_cast<unsigned long>(endpointIndex.count), nodeID.unsignedLongLongValue);
-#endif
-
- for (NSNumber * endpointID in endpointIndex) {
- // Fetch cluster index
- NSArray<NSNumber *> * 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<unsigned long>(clusterIndex.count), nodeID.unsignedLongLongValue, endpointID.unsignedShortValue);
-#endif
-
- for (NSNumber * clusterID in clusterIndex) {
- // Fetch attribute index
- NSArray<NSNumber *> * attributeIndex = [self _fetchAttributeIndexForNodeID:nodeID endpointID:endpointID clusterID:clusterID];
-
-#if ATTRIBUTE_CACHE_VERBOSE_LOGGING
- MTR_LOG_INFO("Fetch got %lu values for attributeIndex @ node 0x%016llX endpoint %u cluster 0x%08lX", static_cast<unsigned long>(attributeIndex.count), nodeID.unsignedLongLongValue, endpointID.unsignedShortValue, clusterID.unsignedLongValue);
-#endif
-
- for (NSNumber * attributeID in attributeIndex) {
- NSDictionary * value = [self _fetchAttributeValueForNodeID:nodeID endpointID:endpointID clusterID:clusterID attributeID:attributeID];
-
-#if ATTRIBUTE_CACHE_VERBOSE_LOGGING
- MTR_LOG_INFO("Fetch got %u values for attribute value @ node 0x%016llX endpoint %u cluster 0x%08lX attribute 0x%08lX", value ? 1 : 0, nodeID.unsignedLongLongValue, endpointID.unsignedShortValue, clusterID.unsignedLongValue, attributeID.unsignedLongValue);
-#endif
-
- if (value) {
- if (!attributesToReturn) {
- attributesToReturn = [NSMutableArray array];
- }
-
- // Construct data-value dictionary and add to array
- MTRAttributePath * path = [MTRAttributePath attributePathWithEndpointID:endpointID clusterID:clusterID attributeID:attributeID];
- [attributesToReturn addObject:@ { MTRAttributePathKey : path, MTRDataKey : value }];
- }
- }
-
- // TODO: Add per-cluster integrity check verification
- }
- }
- });
-
- return attributesToReturn;
-}
-
#ifdef DEBUG
-- (void)unitTestPruneEmptyStoredAttributesBranches
+- (void)unitTestPruneEmptyStoredClusterDataBranches
{
dispatch_sync(_storageDelegateQueue, ^{
- [self _pruneEmptyStoredAttributesBranches];
+ [self _pruneEmptyStoredClusterDataBranches];
});
}
#endif
-- (void)_pruneEmptyStoredAttributesBranches
+- (void)_pruneEmptyStoredClusterDataBranches
{
dispatch_assert_queue(_storageDelegateQueue);
@@ -648,36 +546,10 @@
NSMutableArray<NSNumber *> * clusterIndexCopy = [clusterIndex mutableCopy];
for (NSNumber * clusterID in clusterIndex) {
- // Fetch attribute index
- NSArray<NSNumber *> * attributeIndex = [self _fetchAttributeIndexForNodeID:nodeID endpointID:endpointID clusterID:clusterID];
- NSMutableArray<NSNumber *> * attributeIndexCopy = [attributeIndex mutableCopy];
-
- for (NSNumber * attributeID in attributeIndex) {
- NSDictionary * value = [self _fetchAttributeValueForNodeID:nodeID endpointID:endpointID clusterID:clusterID attributeID:attributeID];
-
- if (!value) {
- [attributeIndexCopy removeObject:attributeID];
- }
- }
-
- 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<unsigned long>(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);
- }
+ // Fetch cluster data, if it exists.
+ MTRDeviceClusterData * clusterData = [self _fetchClusterDataForNodeID:nodeID endpointID:endpointID clusterID:clusterID];
+ if (!clusterData) {
+ [clusterIndexCopy removeObject:clusterID];
}
}
@@ -691,7 +563,7 @@
}
if (!success) {
storeFailures++;
- MTR_LOG_INFO("Store failed in _pruneEmptyStoredAttributesBranches for clusterIndex (%lu) @ node 0x%016llX endpoint %u", static_cast<unsigned long>(clusterIndexCopy.count), nodeID.unsignedLongLongValue, endpointID.unsignedShortValue);
+ MTR_LOG_INFO("Store failed in _pruneEmptyStoredClusterDataBranches for clusterIndex (%lu) @ node 0x%016llX endpoint %u", static_cast<unsigned long>(clusterIndexCopy.count), nodeID.unsignedLongLongValue, endpointID.unsignedShortValue);
}
}
}
@@ -706,7 +578,7 @@
}
if (!success) {
storeFailures++;
- MTR_LOG_INFO("Store failed in _pruneEmptyStoredAttributesBranches for endpointIndex (%lu) @ node 0x%016llX", static_cast<unsigned long>(endpointIndexCopy.count), nodeID.unsignedLongLongValue);
+ MTR_LOG_INFO("Store failed in _pruneEmptyStoredClusterDataBranches for endpointIndex (%lu) @ node 0x%016llX", static_cast<unsigned long>(endpointIndexCopy.count), nodeID.unsignedLongLongValue);
}
}
}
@@ -720,114 +592,23 @@
}
if (!success) {
storeFailures++;
- MTR_LOG_INFO("Store failed in _pruneEmptyStoredAttributesBranches for nodeIndex (%lu)", static_cast<unsigned long>(nodeIndexCopy.count));
+ MTR_LOG_INFO("Store failed in _pruneEmptyStoredClusterDataBranches for nodeIndex (%lu)", static_cast<unsigned long>(nodeIndexCopy.count));
}
}
if (storeFailures) {
- MTR_LOG_ERROR("Store failed in _pruneEmptyStoredAttributesBranches: failure count %lu", static_cast<unsigned long>(storeFailures));
+ MTR_LOG_ERROR("Store failed in _pruneEmptyStoredClusterDataBranches: failure count %lu", static_cast<unsigned long>(storeFailures));
}
}
-- (void)storeAttributeValues:(NSArray<NSDictionary *> *)dataValues forNodeID:(NSNumber *)nodeID
+- (void)_clearStoredClusterDataForNodeID:(NSNumber *)nodeID
{
- dispatch_async(_storageDelegateQueue, ^{
- NSUInteger storeFailures = 0;
+ dispatch_assert_queue(_storageDelegateQueue);
- for (NSDictionary * dataValue in dataValues) {
- MTRAttributePath * path = dataValue[MTRAttributePathKey];
- NSDictionary * value = dataValue[MTRDataKey];
-
-#if ATTRIBUTE_CACHE_VERBOSE_LOGGING
- MTR_LOG_INFO("Attempt to store attribute value @ node 0x%016llX endpoint %u cluster 0x%08lX attribute 0x%08lX", nodeID.unsignedLongLongValue, path.endpoint.unsignedShortValue, path.cluster.unsignedLongValue, path.attribute.unsignedLongValue);
-#endif
-
- BOOL storeFailed = NO;
- // Ensure node index exists
- NSArray<NSNumber *> * 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<NSNumber *> * 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<NSNumber *> * 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;
- }
-
- // TODO: Add per-cluster integrity check calculation and store with cluster
- // TODO: Think about adding more integrity check for endpoint and node levels as well
-
- // Ensure attribute index exists
- NSArray<NSNumber *> * attributeIndex = [self _fetchAttributeIndexForNodeID:nodeID endpointID:path.endpoint clusterID:path.cluster];
- if (!attributeIndex) {
- attributeIndex = [NSArray arrayWithObject:path.attribute];
- storeFailed = ![self _storeAttributeIndex:attributeIndex forNodeID:nodeID endpointID:path.endpoint clusterID:path.cluster];
- } else if (![attributeIndex containsObject:path.attribute]) {
- storeFailed = ![self _storeAttributeIndex:[attributeIndex arrayByAddingObject:path.attribute] forNodeID:nodeID endpointID:path.endpoint clusterID:path.cluster];
- }
- if (storeFailed) {
- storeFailures++;
- MTR_LOG_INFO("Store failed for attributeIndex @ node 0x%016llX endpoint %u cluster 0x%08lX", nodeID.unsignedLongLongValue, path.endpoint.unsignedShortValue, path.cluster.unsignedLongValue);
- continue;
- }
-
- // Store value
- storeFailed = ![self _storeAttributeValue:value forNodeID:nodeID endpointID:path.endpoint clusterID:path.cluster attributeID:path.attribute];
- if (storeFailed) {
- storeFailures++;
- MTR_LOG_INFO("Store failed for attribute value @ node 0x%016llX endpoint %u cluster 0x%08lX attribute 0x%08lX", nodeID.unsignedLongLongValue, path.endpoint.unsignedShortValue, path.cluster.unsignedLongValue, path.attribute.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<unsigned long>(storeFailures));
- }
- });
-}
-
-- (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
NSArray<NSNumber *> * endpointIndex = [self _fetchEndpointIndexForNodeID:nodeID];
@@ -837,30 +618,9 @@
// Fetch cluster index
NSArray<NSNumber *> * 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<NSNumber *> * attributeIndex = [self _fetchAttributeIndexForNodeID:nodeID endpointID:endpointID clusterID:clusterID];
-
- attributesClearAttempts += attributeIndex.count;
- for (NSNumber * attributeID in attributeIndex) {
- BOOL success = [self _deleteAttributeValueForNodeID:nodeID endpointID:endpointID clusterID:clusterID attributeID:attributeID];
- if (!success) {
- MTR_LOG_INFO("Delete failed for attribute value @ node 0x%016llX endpoint %u cluster 0x%08lX attribute 0x%08lX", nodeID.unsignedLongLongValue, endpointID.unsignedShortValue, clusterID.unsignedLongValue, attributeID.unsignedLongValue);
- } else {
- attributesCleared++;
- }
- }
-
- BOOL success = [self _deleteAttributeIndexForNodeID:nodeID endpointID:endpointID clusterID:clusterID];
- if (!success) {
- MTR_LOG_INFO("Delete failed for attributeIndex @ node 0x%016llX endpoint %u cluster 0x%08lX", nodeID.unsignedLongLongValue, endpointID.unsignedShortValue, clusterID.unsignedLongValue);
- } else {
- clustersCleared++;
- }
-
- success = [self _deleteClusterDataForNodeID:nodeID endpointID:endpointID clusterID:clusterID];
+ BOOL 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 {
@@ -878,16 +638,16 @@
BOOL success = [self _deleteEndpointIndexForNodeID:nodeID];
if (!success) {
- MTR_LOG_INFO("Delete failed for endpointrIndex @ node 0x%016llX", nodeID.unsignedLongLongValue);
+ MTR_LOG_INFO("Delete failed for endpointIndex @ node 0x%016llX", nodeID.unsignedLongLongValue);
}
- MTR_LOG_INFO("clearStoredAttributesForNodeID: deleted endpoints %lu/%lu clusters %lu/%lu clusterData %lu/%lu attributes %lu/%lu", static_cast<unsigned long>(endpointsCleared), static_cast<unsigned long>(endpointsClearAttempts), static_cast<unsigned long>(clustersCleared), static_cast<unsigned long>(clustersClearAttempts), static_cast<unsigned long>(clusterDataCleared), static_cast<unsigned long>(clusterDataClearAttempts), static_cast<unsigned long>(attributesCleared), static_cast<unsigned long>(attributesClearAttempts));
+ MTR_LOG_INFO("clearStoredClusterDataForNodeID: deleted endpoints %lu/%lu clusters %lu/%lu", static_cast<unsigned long>(endpointsCleared), static_cast<unsigned long>(endpointsClearAttempts), static_cast<unsigned long>(clusterDataCleared), static_cast<unsigned long>(clusterDataClearAttempts));
}
-- (void)clearStoredAttributesForNodeID:(NSNumber *)nodeID
+- (void)clearStoredClusterDataForNodeID:(NSNumber *)nodeID
{
dispatch_async(_storageDelegateQueue, ^{
- [self _clearStoredAttributesForNodeID:nodeID];
+ [self _clearStoredClusterDataForNodeID:nodeID];
NSArray<NSNumber *> * nodeIndex = [self _fetchNodeIndex];
NSMutableArray<NSNumber *> * nodeIndexCopy = [nodeIndex mutableCopy];
[nodeIndexCopy removeObject:nodeID];
@@ -905,14 +665,14 @@
});
}
-- (void)clearAllStoredAttributes
+- (void)clearAllStoredClusterData
{
dispatch_async(_storageDelegateQueue, ^{
// Fetch node index
NSArray<NSNumber *> * nodeIndex = [self _fetchNodeIndex];
for (NSNumber * nodeID in nodeIndex) {
- [self _clearStoredAttributesForNodeID:nodeID];
+ [self _clearStoredClusterDataForNodeID:nodeID];
}
BOOL success = [self _deleteNodeIndex];
@@ -943,7 +703,10 @@
NSArray<NSNumber *> * endpointIndex = [self _fetchEndpointIndexForNodeID:nodeID];
if (endpointIndex) {
MTR_LOG_ERROR("Persistent attribute cache contains orphaned entry for nodeID %@ - deleting", nodeID);
- [self clearStoredAttributesForNodeID:nodeID];
+ // _clearStoredClusterDataForNodeID because we are are already
+ // on the _storageDelegateQueue and do not need to modify the
+ // node index in this case.
+ [self _clearStoredClusterDataForNodeID:nodeID];
}
MTR_LOG_INFO("Fetch got no value for endpointIndex @ node 0x%016llX", nodeID.unsignedLongLongValue);
@@ -1203,10 +966,10 @@
}
}
- // In the rare event that store fails, allow all attribute store attempts to go through and prune empty branches at the end altogether.
+ // In the rare event that store fails, allow all cluster data 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<unsigned long>(storeFailures));
+ [self _pruneEmptyStoredClusterDataBranches];
+ MTR_LOG_ERROR("Store failed in -storeClusterData:forNodeID: failure count %lu", static_cast<unsigned long>(storeFailures));
}
});
}
diff --git a/src/darwin/Framework/CHIP/MTRDevice_Internal.h b/src/darwin/Framework/CHIP/MTRDevice_Internal.h
index 2883306..57ee544 100644
--- a/src/darwin/Framework/CHIP/MTRDevice_Internal.h
+++ b/src/darwin/Framework/CHIP/MTRDevice_Internal.h
@@ -30,9 +30,6 @@
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
*/
diff --git a/src/darwin/Framework/CHIPTests/MTRDeviceTests.m b/src/darwin/Framework/CHIPTests/MTRDeviceTests.m
index 4f97026..d13f319 100644
--- a/src/darwin/Framework/CHIPTests/MTRDeviceTests.m
+++ b/src/darwin/Framework/CHIPTests/MTRDeviceTests.m
@@ -179,9 +179,9 @@
// Restore testing setting to previous state, and remove all persisted attributes
MTRDeviceControllerLocalTestStorage.localTestStorageEnabled = slocalTestStorageEnabledBeforeUnitTest;
- [sController.controllerDataStore clearAllStoredAttributes];
- NSArray * storedAttributesAfterClear = [sController.controllerDataStore getStoredAttributesForNodeID:@(kDeviceId)];
- XCTAssertEqual(storedAttributesAfterClear.count, 0);
+ [sController.controllerDataStore clearAllStoredClusterData];
+ NSDictionary * storedClusterDataAfterClear = [sController.controllerDataStore getStoredClusterDataForNodeID:@(kDeviceId)];
+ XCTAssertEqual(storedClusterDataAfterClear.count, 0);
MTRDeviceController * controller = sController;
XCTAssertNotNil(controller);
@@ -1346,9 +1346,9 @@
- (void)test017_TestMTRDeviceBasics
{
// Ensure the test starts with clean slate, even with MTRDeviceControllerLocalTestStorage enabled
- [sController.controllerDataStore clearAllStoredAttributes];
- NSArray * storedAttributesAfterClear = [sController.controllerDataStore getStoredAttributesForNodeID:@(kDeviceId)];
- XCTAssertEqual(storedAttributesAfterClear.count, 0);
+ [sController.controllerDataStore clearAllStoredClusterData];
+ NSDictionary * storedClusterDataAfterClear = [sController.controllerDataStore getStoredClusterDataForNodeID:@(kDeviceId)];
+ XCTAssertEqual(storedClusterDataAfterClear.count, 0);
__auto_type * device = [MTRDevice deviceWithNodeID:kDeviceId deviceController:sController];
dispatch_queue_t queue = dispatch_get_main_queue();
@@ -2571,9 +2571,9 @@
- (void)test029_MTRDeviceWriteCoalescing
{
// Ensure the test starts with clean slate, even with MTRDeviceControllerLocalTestStorage enabled
- [sController.controllerDataStore clearAllStoredAttributes];
- NSArray * storedAttributesAfterClear = [sController.controllerDataStore getStoredAttributesForNodeID:@(kDeviceId)];
- XCTAssertEqual(storedAttributesAfterClear.count, 0);
+ [sController.controllerDataStore clearAllStoredClusterData];
+ NSDictionary * storedClusterDataAfterClear = [sController.controllerDataStore getStoredClusterDataForNodeID:@(kDeviceId)];
+ XCTAssertEqual(storedClusterDataAfterClear.count, 0);
__auto_type * device = [MTRDevice deviceWithNodeID:kDeviceId deviceController:sController];
dispatch_queue_t queue = dispatch_get_main_queue();
@@ -2910,9 +2910,9 @@
// First start with clean slate by removing the MTRDevice and clearing the persisted cache
__auto_type * device = [MTRDevice deviceWithNodeID:@(kDeviceId) controller:sController];
[sController removeDevice:device];
- [sController.controllerDataStore clearAllStoredAttributes];
- NSArray * storedAttributesAfterClear = [sController.controllerDataStore getStoredAttributesForNodeID:@(kDeviceId)];
- XCTAssertEqual(storedAttributesAfterClear.count, 0);
+ [sController.controllerDataStore clearAllStoredClusterData];
+ NSDictionary * storedClusterDataAfterClear = [sController.controllerDataStore getStoredClusterDataForNodeID:@(kDeviceId)];
+ XCTAssertEqual(storedClusterDataAfterClear.count, 0);
// Now recreate device and get subscription primed
device = [MTRDevice deviceWithNodeID:@(kDeviceId) controller:sController];
@@ -2934,13 +2934,8 @@
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];
@@ -2971,8 +2966,13 @@
// 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.
- NSArray * dataStoreValuesAfterSecondSubscription = [sController.controllerDataStore getStoredAttributesForNodeID:@(kDeviceId)];
- NSUInteger storedAttributeCountDifferenceFromMTRDeviceReport = dataStoreValuesAfterSecondSubscription.count - attributesReportedWithSecondSubscription;
+ NSDictionary * storedClusterDataAfterSecondSubscription = [sController.controllerDataStore getStoredClusterDataForNodeID:@(kDeviceId)];
+ NSUInteger dataStoreAttributeCountAfterSecondSubscription = 0;
+ for (NSNumber * clusterID in storedClusterDataAfterSecondSubscription) {
+ MTRDeviceClusterData * clusterData = storedClusterDataAfterSecondSubscription[clusterID];
+ dataStoreAttributeCountAfterSecondSubscription += clusterData.attributes.count;
+ }
+ NSUInteger storedAttributeCountDifferenceFromMTRDeviceReport = dataStoreAttributeCountAfterSecondSubscription - attributesReportedWithSecondSubscription;
XCTAssertTrue(storedAttributeCountDifferenceFromMTRDeviceReport > 300);
}
diff --git a/src/darwin/Framework/CHIPTests/MTRPerControllerStorageTests.m b/src/darwin/Framework/CHIPTests/MTRPerControllerStorageTests.m
index df98530..bea49d9 100644
--- a/src/darwin/Framework/CHIPTests/MTRPerControllerStorageTests.m
+++ b/src/darwin/Framework/CHIPTests/MTRPerControllerStorageTests.m
@@ -19,6 +19,7 @@
// system dependencies
#import <XCTest/XCTest.h>
+#import "MTRDeviceControllerLocalTestStorage.h"
#import "MTRDeviceTestDelegate.h"
#import "MTRDevice_Internal.h"
#import "MTRErrorTestUtils.h"
@@ -181,6 +182,7 @@
@implementation MTRPerControllerStorageTests {
dispatch_queue_t _storageQueue;
+ BOOL _localTestStorageEnabledBeforeUnitTest;
}
- (void)setUp
@@ -189,6 +191,12 @@
[super setUp];
[self setContinueAfterFailure:NO];
+ // Make sure local test storage is off, because we assume that the storage
+ // delegate we provide is in fact used for local storage as part of our
+ // tests.
+ _localTestStorageEnabledBeforeUnitTest = MTRDeviceControllerLocalTestStorage.localTestStorageEnabled;
+ MTRDeviceControllerLocalTestStorage.localTestStorageEnabled = NO;
+
_storageQueue = dispatch_queue_create("test.storage.queue", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
}
@@ -197,6 +205,10 @@
// Per-test teardown, runs after each test.
[self stopFactory];
_storageQueue = nil;
+
+ // Restore local test storage setting to previous state.
+ MTRDeviceControllerLocalTestStorage.localTestStorageEnabled = _localTestStorageEnabledBeforeUnitTest;
+
[super tearDown];
}
@@ -1081,132 +1093,137 @@
XCTAssertEqualObjects(controller.controllerNodeID, nodeID);
- NSArray * testAttributes = @[
- @{ MTRAttributePathKey : [MTRAttributePath attributePathWithEndpointID:@(1) clusterID:@(1) attributeID:@(1)], MTRDataKey : @ { MTRTypeKey : MTRUnsignedIntegerValueType, MTRValueKey : @(111) } },
- @{ MTRAttributePathKey : [MTRAttributePath attributePathWithEndpointID:@(1) clusterID:@(1) attributeID:@(2)], MTRDataKey : @ { MTRTypeKey : MTRUnsignedIntegerValueType, MTRValueKey : @(112) } },
- @{ MTRAttributePathKey : [MTRAttributePath attributePathWithEndpointID:@(1) clusterID:@(1) attributeID:@(3)], MTRDataKey : @ { MTRTypeKey : MTRUnsignedIntegerValueType, MTRValueKey : @(113) } },
-
- @{ MTRAttributePathKey : [MTRAttributePath attributePathWithEndpointID:@(1) clusterID:@(2) attributeID:@(1)], MTRDataKey : @ { MTRTypeKey : MTRUnsignedIntegerValueType, MTRValueKey : @(121) } },
- @{ MTRAttributePathKey : [MTRAttributePath attributePathWithEndpointID:@(1) clusterID:@(2) attributeID:@(2)], MTRDataKey : @ { MTRTypeKey : MTRUnsignedIntegerValueType, MTRValueKey : @(122) } },
- @{ MTRAttributePathKey : [MTRAttributePath attributePathWithEndpointID:@(1) clusterID:@(2) attributeID:@(3)], MTRDataKey : @ { MTRTypeKey : MTRUnsignedIntegerValueType, MTRValueKey : @(123) } },
-
- @{ MTRAttributePathKey : [MTRAttributePath attributePathWithEndpointID:@(2) clusterID:@(1) attributeID:@(1)], MTRDataKey : @ { MTRTypeKey : MTRUnsignedIntegerValueType, MTRValueKey : @(211) } },
- @{ MTRAttributePathKey : [MTRAttributePath attributePathWithEndpointID:@(2) clusterID:@(1) attributeID:@(2)], MTRDataKey : @ { MTRTypeKey : MTRUnsignedIntegerValueType, MTRValueKey : @(212) } },
- @{ MTRAttributePathKey : [MTRAttributePath attributePathWithEndpointID:@(2) clusterID:@(1) attributeID:@(3)], MTRDataKey : @ { MTRTypeKey : MTRUnsignedIntegerValueType, MTRValueKey : @(213) } },
- ];
- [controller.controllerDataStore storeAttributeValues:testAttributes forNodeID:@(1001)];
- [controller.controllerDataStore storeAttributeValues:testAttributes forNodeID:@(1002)];
- [controller.controllerDataStore storeAttributeValues:testAttributes forNodeID:@(1003)];
-
MTRDeviceClusterData * testClusterData1 = [[MTRDeviceClusterData alloc] init];
testClusterData1.dataVersion = @(1);
+ testClusterData1.attributes = @{
+ @(1) : @ { MTRTypeKey : MTRUnsignedIntegerValueType, MTRValueKey : @(111) },
+ @(2) : @ { MTRTypeKey : MTRUnsignedIntegerValueType, MTRValueKey : @(112) },
+ @(3) : @ { MTRTypeKey : MTRUnsignedIntegerValueType, MTRValueKey : @(113) },
+ };
MTRDeviceClusterData * testClusterData2 = [[MTRDeviceClusterData alloc] init];
testClusterData2.dataVersion = @(2);
+ testClusterData2.attributes = @{
+ @(1) : @ { MTRTypeKey : MTRUnsignedIntegerValueType, MTRValueKey : @(121) },
+ @(2) : @ { MTRTypeKey : MTRUnsignedIntegerValueType, MTRValueKey : @(122) },
+ @(3) : @ { MTRTypeKey : MTRUnsignedIntegerValueType, MTRValueKey : @(123) },
+ };
MTRDeviceClusterData * testClusterData3 = [[MTRDeviceClusterData alloc] init];
testClusterData3.dataVersion = @(3);
+ testClusterData3.attributes = @{
+ @(1) : @ { MTRTypeKey : MTRUnsignedIntegerValueType, MTRValueKey : @(211) },
+ @(2) : @ { MTRTypeKey : MTRUnsignedIntegerValueType, MTRValueKey : @(212) },
+ @(3) : @ { MTRTypeKey : MTRUnsignedIntegerValueType, MTRValueKey : @(213) },
+ };
NSDictionary<MTRClusterPath *, MTRDeviceClusterData *> * testClusterData = @{
[MTRClusterPath clusterPathWithEndpointID:@(1) clusterID:@(1)] : testClusterData1,
[MTRClusterPath clusterPathWithEndpointID:@(1) clusterID:@(2)] : testClusterData2,
- [MTRClusterPath clusterPathWithEndpointID:@(1) clusterID:@(3)] : testClusterData3,
+ [MTRClusterPath clusterPathWithEndpointID:@(2) clusterID:@(3)] : testClusterData3,
};
[controller.controllerDataStore storeClusterData:testClusterData forNodeID:@(1001)];
+ [controller.controllerDataStore storeClusterData:testClusterData forNodeID:@(1002)];
+ [controller.controllerDataStore storeClusterData:testClusterData forNodeID:@(1003)];
- // Check values are written and can be fetched
- NSArray * dataStoreValues = [controller.controllerDataStore getStoredAttributesForNodeID:@(1001)];
- XCTAssertEqual(dataStoreValues.count, 9);
- dataStoreValues = [controller.controllerDataStore getStoredAttributesForNodeID:@(1002)];
- XCTAssertEqual(dataStoreValues.count, 9);
- dataStoreValues = [controller.controllerDataStore getStoredAttributesForNodeID:@(1003)];
- XCTAssertEqual(dataStoreValues.count, 9);
-
- // Check values
- NSUInteger unexpectedValues = 0;
- for (NSDictionary * responseValue in dataStoreValues) {
- MTRAttributePath * path = responseValue[MTRAttributePathKey];
- XCTAssertNotNil(path);
- NSDictionary * dataValue = responseValue[MTRDataKey];
- XCTAssertNotNil(dataValue);
- NSString * type = dataValue[MTRTypeKey];
- XCTAssertNotNil(type);
- XCTAssertEqualObjects(type, MTRUnsignedIntegerValueType);
- NSNumber * value = dataValue[MTRValueKey];
- XCTAssertNotNil(value);
-
- if ([path.endpoint isEqualToNumber:@(1)] && [path.cluster isEqualToNumber:@(1)] && [path.attribute isEqualToNumber:@(1)]) {
- XCTAssertEqualObjects(value, @(111));
- } else if ([path.endpoint isEqualToNumber:@(1)] && [path.cluster isEqualToNumber:@(1)] && [path.attribute isEqualToNumber:@(2)]) {
- XCTAssertEqualObjects(value, @(112));
- } else if ([path.endpoint isEqualToNumber:@(1)] && [path.cluster isEqualToNumber:@(1)] && [path.attribute isEqualToNumber:@(3)]) {
- XCTAssertEqualObjects(value, @(113));
- } else if ([path.endpoint isEqualToNumber:@(1)] && [path.cluster isEqualToNumber:@(2)] && [path.attribute isEqualToNumber:@(1)]) {
- XCTAssertEqualObjects(value, @(121));
- } else if ([path.endpoint isEqualToNumber:@(1)] && [path.cluster isEqualToNumber:@(2)] && [path.attribute isEqualToNumber:@(2)]) {
- XCTAssertEqualObjects(value, @(122));
- } else if ([path.endpoint isEqualToNumber:@(1)] && [path.cluster isEqualToNumber:@(2)] && [path.attribute isEqualToNumber:@(3)]) {
- XCTAssertEqualObjects(value, @(123));
- } else if ([path.endpoint isEqualToNumber:@(2)] && [path.cluster isEqualToNumber:@(1)] && [path.attribute isEqualToNumber:@(1)]) {
- XCTAssertEqualObjects(value, @(211));
- } else if ([path.endpoint isEqualToNumber:@(2)] && [path.cluster isEqualToNumber:@(1)] && [path.attribute isEqualToNumber:@(2)]) {
- XCTAssertEqualObjects(value, @(212));
- } else if ([path.endpoint isEqualToNumber:@(2)] && [path.cluster isEqualToNumber:@(1)] && [path.attribute isEqualToNumber:@(3)]) {
- XCTAssertEqualObjects(value, @(213));
- } else {
- unexpectedValues++;
+ for (NSNumber * nodeID in @[ @(1001), @(1002), @(1003) ]) {
+ NSDictionary<MTRClusterPath *, MTRDeviceClusterData *> * dataStoreClusterData = [controller.controllerDataStore getStoredClusterDataForNodeID:nodeID];
+ for (MTRClusterPath * path in testClusterData) {
+ XCTAssertEqualObjects(testClusterData[path], dataStoreClusterData[path]);
}
}
- XCTAssertEqual(unexpectedValues, 0);
- NSDictionary<MTRClusterPath *, MTRDeviceClusterData *> * dataStoreClusterData = [controller.controllerDataStore getStoredClusterDataForNodeID:@(1001)];
- for (MTRClusterPath * path in testClusterData) {
- XCTAssertEqualObjects(testClusterData[path].dataVersion, dataStoreClusterData[path].dataVersion);
+ [controller.controllerDataStore clearStoredClusterDataForNodeID:@(1001)];
+ XCTAssertNil([controller.controllerDataStore getStoredClusterDataForNodeID:@(1001)]);
+ for (NSNumber * nodeID in @[ @(1002), @(1003) ]) {
+ NSDictionary<MTRClusterPath *, MTRDeviceClusterData *> * dataStoreClusterData = [controller.controllerDataStore getStoredClusterDataForNodeID:nodeID];
+ for (MTRClusterPath * path in testClusterData) {
+ XCTAssertEqualObjects(testClusterData[path], dataStoreClusterData[path]);
+ }
}
- [controller.controllerDataStore clearStoredAttributesForNodeID:@(1001)];
- dataStoreValues = [controller.controllerDataStore getStoredAttributesForNodeID:@(1001)];
- XCTAssertEqual(dataStoreValues.count, 0);
- dataStoreValues = [controller.controllerDataStore getStoredAttributesForNodeID:@(1002)];
- XCTAssertEqual(dataStoreValues.count, 9);
- dataStoreValues = [controller.controllerDataStore getStoredAttributesForNodeID:@(1003)];
- XCTAssertEqual(dataStoreValues.count, 9);
-
- [controller.controllerDataStore clearAllStoredAttributes];
- dataStoreValues = [controller.controllerDataStore getStoredAttributesForNodeID:@(1001)];
- XCTAssertEqual(dataStoreValues.count, 0);
- dataStoreValues = [controller.controllerDataStore getStoredAttributesForNodeID:@(1002)];
- XCTAssertEqual(dataStoreValues.count, 0);
- dataStoreValues = [controller.controllerDataStore getStoredAttributesForNodeID:@(1003)];
- XCTAssertEqual(dataStoreValues.count, 0);
+ [controller.controllerDataStore clearAllStoredClusterData];
+ for (NSNumber * nodeID in @[ @(1001), @(1002), @(1003) ]) {
+ XCTAssertNil([controller.controllerDataStore getStoredClusterDataForNodeID:nodeID]);
+ }
// Test MTRDeviceControllerDataStore _pruneEmptyStoredAttributesBranches
// - Clear cache
- // - Store an attribute
- // - Manually delete it from the test storage delegate
+ // - Store some cluster data
+ // - Manually delete parts of the data from the test storage delegate
// - Call _pruneEmptyStoredAttributesBranches
- [controller.controllerDataStore clearAllStoredAttributes];
+ [controller.controllerDataStore storeClusterData:testClusterData forNodeID:@(2001)];
+ [controller.controllerDataStore storeClusterData:testClusterData forNodeID:@(2002)];
- NSArray * testAttribute = @[
- @{ MTRAttributePathKey : [MTRAttributePath attributePathWithEndpointID:@(1) clusterID:@(1) attributeID:@(1)], MTRDataKey : @ { MTRTypeKey : MTRUnsignedIntegerValueType, MTRValueKey : @(111) } },
- ];
- [controller.controllerDataStore storeAttributeValues:testAttribute forNodeID:@(2001)];
+ NSString * testClusterIndexKey1 = [controller.controllerDataStore _clusterIndexKeyForNodeID:@(2001) endpointID:@(1)];
+ NSString * testClusterIndexKey2 = [controller.controllerDataStore _clusterIndexKeyForNodeID:@(2001) endpointID:@(2)];
+ NSString * testClusterIndexKey3 = [controller.controllerDataStore _clusterIndexKeyForNodeID:@(2002) endpointID:@(1)];
+ NSString * testClusterIndexKey4 = [controller.controllerDataStore _clusterIndexKeyForNodeID:@(2002) endpointID:@(2)];
+ NSString * testEndpointIndexKey1 = [controller.controllerDataStore _endpointIndexKeyForNodeID:@(2001)];
+ NSString * testEndpointIndexKey2 = [controller.controllerDataStore _endpointIndexKeyForNodeID:@(2002)];
+ NSString * testNodeIndexKey = @"attrCacheNodeIndex";
// store is async, so remove on the same queue to ensure order
dispatch_sync(_storageQueue, ^{
- NSString * testAttributeValueKey = [controller.controllerDataStore _attributeValueKeyForNodeID:@(2001) endpointID:@(1) clusterID:@(1) attributeID:@(1)];
- [storageDelegate controller:controller removeValueForKey:testAttributeValueKey securityLevel:MTRStorageSecurityLevelSecure sharingType:MTRStorageSharingTypeNotShared];
- });
- [controller.controllerDataStore unitTestPruneEmptyStoredAttributesBranches];
+ // Ensure that the indices we expect are populated.
+ XCTAssertNotNil([storageDelegate controller:controller valueForKey:testClusterIndexKey1 securityLevel:MTRStorageSecurityLevelSecure sharingType:MTRStorageSharingTypeNotShared]);
+ XCTAssertNotNil([storageDelegate controller:controller valueForKey:testClusterIndexKey2 securityLevel:MTRStorageSecurityLevelSecure sharingType:MTRStorageSharingTypeNotShared]);
+ XCTAssertNotNil([storageDelegate controller:controller valueForKey:testClusterIndexKey3 securityLevel:MTRStorageSecurityLevelSecure sharingType:MTRStorageSharingTypeNotShared]);
+ XCTAssertNotNil([storageDelegate controller:controller valueForKey:testClusterIndexKey4 securityLevel:MTRStorageSecurityLevelSecure sharingType:MTRStorageSharingTypeNotShared]);
+ XCTAssertNotNil([storageDelegate controller:controller valueForKey:testEndpointIndexKey1 securityLevel:MTRStorageSecurityLevelSecure sharingType:MTRStorageSharingTypeNotShared]);
+ XCTAssertNotNil([storageDelegate controller:controller valueForKey:testEndpointIndexKey2 securityLevel:MTRStorageSecurityLevelSecure sharingType:MTRStorageSharingTypeNotShared]);
+ XCTAssertNotNil([storageDelegate controller:controller valueForKey:testNodeIndexKey securityLevel:MTRStorageSecurityLevelSecure sharingType:MTRStorageSharingTypeNotShared]);
- // Now check the indexes are pruned
- NSString * testAttributeIndexKey = [controller.controllerDataStore _attributeIndexKeyForNodeID:@(2001) endpointID:@(1) clusterID:@(1)];
- id testAttributeIndex = [storageDelegate controller:controller valueForKey:testAttributeIndexKey securityLevel:MTRStorageSecurityLevelSecure sharingType:MTRStorageSharingTypeNotShared];
- XCTAssertNil(testAttributeIndex);
- NSString * testClusterIndexKey = [controller.controllerDataStore _clusterIndexKeyForNodeID:@(2001) endpointID:@(1)];
- id testClusterIndex = [storageDelegate controller:controller valueForKey:testClusterIndexKey securityLevel:MTRStorageSecurityLevelSecure sharingType:MTRStorageSharingTypeNotShared];
- XCTAssertNil(testClusterIndex);
- NSString * testEndpointIndexKey = [controller.controllerDataStore _endpointIndexKeyForNodeID:@(2001)];
- id testEndpointIndex = [storageDelegate controller:controller valueForKey:testEndpointIndexKey securityLevel:MTRStorageSecurityLevelSecure sharingType:MTRStorageSharingTypeNotShared];
- XCTAssertNil(testEndpointIndex);
- id testNodeIndex = [storageDelegate controller:controller valueForKey:@"attrCacheNodeIndex" securityLevel:MTRStorageSecurityLevelSecure sharingType:MTRStorageSharingTypeNotShared];
+ // Remove all three MTRDeviceClusterData for node 2001
+ NSString * testClusterDataKey = [controller.controllerDataStore _clusterDataKeyForNodeID:@(2001) endpointID:@(1) clusterID:@(1)];
+ [storageDelegate controller:controller removeValueForKey:testClusterDataKey securityLevel:MTRStorageSecurityLevelSecure sharingType:MTRStorageSharingTypeNotShared];
+ testClusterDataKey = [controller.controllerDataStore _clusterDataKeyForNodeID:@(2001) endpointID:@(1) clusterID:@(2)];
+ [storageDelegate controller:controller removeValueForKey:testClusterDataKey securityLevel:MTRStorageSecurityLevelSecure sharingType:MTRStorageSharingTypeNotShared];
+ testClusterDataKey = [controller.controllerDataStore _clusterDataKeyForNodeID:@(2001) endpointID:@(2) clusterID:@(3)];
+ [storageDelegate controller:controller removeValueForKey:testClusterDataKey securityLevel:MTRStorageSecurityLevelSecure sharingType:MTRStorageSharingTypeNotShared];
+
+ // Remove the two MTRDeviceClusterData under endpoint 1 for node 2002
+ testClusterDataKey = [controller.controllerDataStore _clusterDataKeyForNodeID:@(2002) endpointID:@(1) clusterID:@(1)];
+ [storageDelegate controller:controller removeValueForKey:testClusterDataKey securityLevel:MTRStorageSecurityLevelSecure sharingType:MTRStorageSharingTypeNotShared];
+ testClusterDataKey = [controller.controllerDataStore _clusterDataKeyForNodeID:@(2002) endpointID:@(1) clusterID:@(2)];
+ [storageDelegate controller:controller removeValueForKey:testClusterDataKey securityLevel:MTRStorageSecurityLevelSecure sharingType:MTRStorageSharingTypeNotShared];
+ });
+
+ [controller.controllerDataStore unitTestPruneEmptyStoredClusterDataBranches];
+
+ // Now check the indexes are pruned. There should be no more cluster
+ // indices or endpoint indices for node 2001.
+ id testClusterIndex1 = [storageDelegate controller:controller valueForKey:testClusterIndexKey1 securityLevel:MTRStorageSecurityLevelSecure sharingType:MTRStorageSharingTypeNotShared];
+ XCTAssertNil(testClusterIndex1);
+ id testClusterIndex2 = [storageDelegate controller:controller valueForKey:testClusterIndexKey2 securityLevel:MTRStorageSecurityLevelSecure sharingType:MTRStorageSharingTypeNotShared];
+ XCTAssertNil(testClusterIndex2);
+ id testEndpointIndex1 = [storageDelegate controller:controller valueForKey:testEndpointIndexKey1 securityLevel:MTRStorageSecurityLevelSecure sharingType:MTRStorageSharingTypeNotShared];
+ XCTAssertNil(testEndpointIndex1);
+
+ // There should be no more cluster index for endpoint 1 for node 2, but
+ // we should still have a cluster index for endpoint 2, and an endpoint index.
+ id testClusterIndex3 = [storageDelegate controller:controller valueForKey:testClusterIndexKey3 securityLevel:MTRStorageSecurityLevelSecure sharingType:MTRStorageSharingTypeNotShared];
+ XCTAssertNil(testClusterIndex3);
+ id testClusterIndex4 = [storageDelegate controller:controller valueForKey:testClusterIndexKey4 securityLevel:MTRStorageSecurityLevelSecure sharingType:MTRStorageSharingTypeNotShared];
+ XCTAssertNotNil(testClusterIndex4);
+ id testEndpointIndex2 = [storageDelegate controller:controller valueForKey:testEndpointIndexKey2 securityLevel:MTRStorageSecurityLevelSecure sharingType:MTRStorageSharingTypeNotShared];
+ XCTAssertNotNil(testClusterIndex4);
+
+ // We should still have a node index.
+ id testNodeIndex = [storageDelegate controller:controller valueForKey:testNodeIndexKey securityLevel:MTRStorageSecurityLevelSecure sharingType:MTRStorageSharingTypeNotShared];
+ XCTAssertNotNil(testNodeIndex);
+
+ // Again, remove on the storage queue to ensure order.
+ dispatch_sync(_storageQueue, ^{
+ NSString * testClusterDataKey = [controller.controllerDataStore _clusterDataKeyForNodeID:@(2002) endpointID:@(2) clusterID:@(3)];
+ [storageDelegate controller:controller removeValueForKey:testClusterDataKey securityLevel:MTRStorageSecurityLevelSecure sharingType:MTRStorageSharingTypeNotShared];
+ });
+
+ [controller.controllerDataStore unitTestPruneEmptyStoredClusterDataBranches];
+
+ // All the indices should be pruned now.
+ testClusterIndex4 = [storageDelegate controller:controller valueForKey:testClusterIndexKey4 securityLevel:MTRStorageSecurityLevelSecure sharingType:MTRStorageSharingTypeNotShared];
+ XCTAssertNil(testClusterIndex4);
+ testEndpointIndex2 = [storageDelegate controller:controller valueForKey:testEndpointIndexKey2 securityLevel:MTRStorageSecurityLevelSecure sharingType:MTRStorageSharingTypeNotShared];
+ XCTAssertNil(testClusterIndex4);
+ testNodeIndex = [storageDelegate controller:controller valueForKey:testNodeIndexKey securityLevel:MTRStorageSecurityLevelSecure sharingType:MTRStorageSharingTypeNotShared];
XCTAssertNil(testNodeIndex);
// Now test bulk write
@@ -1275,11 +1292,11 @@
[storageDelegate controller:controller storeValues:testBulkValues securityLevel:MTRStorageSecurityLevelSecure sharingType:MTRStorageSharingTypeNotShared];
});
// Verify that the store resulted in the correct values
- dataStoreClusterData = [controller.controllerDataStore getStoredClusterDataForNodeID:@(3001)];
+ NSDictionary<MTRClusterPath *, MTRDeviceClusterData *> * dataStoreClusterData = [controller.controllerDataStore getStoredClusterDataForNodeID:@(3001)];
XCTAssertEqualObjects(dataStoreClusterData, bulkTestClusterDataDictionary);
// clear information before the next test
- [controller.controllerDataStore clearStoredAttributesForNodeID:@(3001)];
+ [controller.controllerDataStore clearStoredClusterDataForNodeID:@(3001)];
// Now test bulk store through data store
[controller.controllerDataStore storeClusterData:bulkTestClusterDataDictionary forNodeID:@(3001)];
@@ -1356,7 +1373,6 @@
[self waitForExpectations:@[ subscriptionExpectation ] timeout:60];
NSUInteger dataStoreValuesCount = 0;
-#if MTRDEVICE_ATTRIBUTE_CACHE_STORE_ATTRIBUTES_BY_CLUSTER
NSDictionary<MTRClusterPath *, MTRDeviceClusterData *> * dataStoreClusterData = [controller.controllerDataStore getStoredClusterDataForNodeID:deviceID];
for (MTRClusterPath * path in dataStoreClusterData) {
MTRDeviceClusterData * data = dataStoreClusterData[path];
@@ -1367,21 +1383,6 @@
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) {
- MTRAttributePath * path = responseValue[MTRAttributePathKey];
- XCTAssertNotNil(path);
- NSDictionary * dataValue = responseValue[MTRDataKey];
- XCTAssertNotNil(dataValue);
-
- 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];
@@ -1389,7 +1390,6 @@
// 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) {
@@ -1400,19 +1400,6 @@
}
}
}
-#else
- for (NSDictionary * responseValue in dataStoreValues) {
- MTRAttributePath * path = responseValue[MTRAttributePathKey];
- XCTAssertNotNil(path);
- NSDictionary * dataValue = responseValue[MTRDataKey];
- XCTAssertNotNil(dataValue);
-
- NSDictionary * dataValueFromMTRDevice = [newDevice readAttributeWithEndpointID:path.endpoint clusterID:path.cluster attributeID:path.attribute params:nil];
- if (![newDevice _attributeDataValue:dataValue isEqualToDataValue:dataValueFromMTRDevice]) {
- 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.
diff --git a/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestDeclarations.h b/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestDeclarations.h
index b3ef032..6f5755c 100644
--- a/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestDeclarations.h
+++ b/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestDeclarations.h
@@ -25,18 +25,14 @@
@class MTRDeviceClusterData;
// MTRDeviceControllerDataStore.h includes C++ header, and so we need to declare the methods separately
@protocol MTRDeviceControllerDataStoreAttributeStoreMethods
-- (nullable NSArray<NSDictionary *> *)getStoredAttributesForNodeID:(NSNumber *)nodeID;
- (nullable NSDictionary<MTRClusterPath *, MTRDeviceClusterData *> *)getStoredClusterDataForNodeID:(NSNumber *)nodeID;
-- (void)storeAttributeValues:(NSArray<NSDictionary *> *)dataValues forNodeID:(NSNumber *)nodeID;
- (void)storeClusterData:(NSDictionary<MTRClusterPath *, MTRDeviceClusterData *> *)clusterData forNodeID:(NSNumber *)nodeID;
-- (void)clearStoredAttributesForNodeID:(NSNumber *)nodeID;
-- (void)clearAllStoredAttributes;
-- (void)unitTestPruneEmptyStoredAttributesBranches;
+- (void)clearStoredClusterDataForNodeID:(NSNumber *)nodeID;
+- (void)clearAllStoredClusterData;
+- (void)unitTestPruneEmptyStoredClusterDataBranches;
- (NSString *)_endpointIndexKeyForNodeID:(NSNumber *)nodeID;
- (NSString *)_clusterIndexKeyForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID;
- (NSString *)_clusterDataKeyForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID;
-- (NSString *)_attributeIndexKeyForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID;
-- (NSString *)_attributeValueKeyForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID attributeID:(NSNumber *)attributeID;
@end
// Declare internal methods for testing