@@ -196,6 +196,11 @@ - (void)storeValue:(MTRDeviceDataValueDictionary _Nullable)value forAttribute:(N
196
196
_attributes[attribute] = value;
197
197
}
198
198
199
+ - (void)_removeValueForAttribute:(NSNumber *)attribute
200
+ {
201
+ [_attributes removeObjectForKey:attribute];
202
+ }
203
+
199
204
- (NSDictionary<NSNumber *, MTRDeviceDataValueDictionary> *)attributes
200
205
{
201
206
return _attributes;
@@ -1374,6 +1379,18 @@ - (void)_setCachedAttributeValue:(MTRDeviceDataValueDictionary _Nullable)value f
1374
1379
_clusterDataToPersist[clusterPath] = clusterData;
1375
1380
}
1376
1381
1382
+ - (void)_removeCachedAttributeValue:(MTRClusterPath *)clusterPath forPath:(MTRAttributePath *)attributePath
1383
+ {
1384
+ os_unfair_lock_assert_owner(&self->_lock);
1385
+
1386
+ if (_clusterDataToPersist == nil) {
1387
+ return;
1388
+ }
1389
+ auto * clusterData = [_clusterDataToPersist objectForKey:clusterPath];
1390
+ [clusterData _removeValueForAttribute:attributePath.attribute];
1391
+ [_clusterDataToPersist setObject:clusterData forKey:clusterPath];
1392
+ }
1393
+
1377
1394
- (void)_createDataVersionFilterListFromDictionary:(NSDictionary<MTRClusterPath *, NSNumber *> *)dataVersions dataVersionFilterList:(DataVersionFilter **)dataVersionFilterList count:(size_t *)count sizeReduction:(size_t)sizeReduction
1378
1395
{
1379
1396
size_t maxDataVersionFilterSize = dataVersions.count;
@@ -2431,7 +2448,7 @@ - (BOOL)_attributeDataValue:(NSDictionary *)one isEqualToDataValue:(NSDictionary
2431
2448
}
2432
2449
2433
2450
// Utility to return data value dictionary without data version
2434
- - (NSDictionary *)_dataValueWithoutDataVersion : (NSDictionary *)attributeValue ;
2451
+ - (NSDictionary *)_dataValueWithoutDataVersion:(NSDictionary *)attributeValue
2435
2452
{
2436
2453
// Sanity check for nil - return the same input to fail gracefully
2437
2454
if (!attributeValue || !attributeValue[MTRTypeKey]) {
@@ -2493,6 +2510,137 @@ - (BOOL)_attributeAffectsDeviceConfiguration:(MTRAttributePath *)attributePath
2493
2510
return NO;
2494
2511
}
2495
2512
2513
+ - (BOOL)_needsPruningOfEndpointsAndClusters:(MTRAttributePath *)attributePath
2514
+ {
2515
+ // Check for attributes in the descriptor cluster that could cause removal of endpoints and clusters.
2516
+ if (attributePath.cluster.unsignedLongValue == MTRClusterIDTypeDescriptorID) {
2517
+ switch (attributePath.attribute.unsignedLongValue) {
2518
+ case MTRAttributeIDTypeClusterDescriptorAttributePartsListID:
2519
+ case MTRAttributeIDTypeClusterDescriptorAttributeServerListID:
2520
+ return YES;
2521
+ }
2522
+ }
2523
+
2524
+ // Check for global attribute - attribute list that could cause removal of attributes.
2525
+ switch (attributePath.attribute.unsignedLongValue) {
2526
+ case MTRAttributeIDTypeGlobalAttributeAttributeListID:
2527
+ return YES;
2528
+ }
2529
+ return NO;
2530
+ }
2531
+
2532
+ - (void)_pruneOrphanedEndpointsAndClusters:(MTRAttributePath *)attributePath
2533
+ previousValue:(NSDictionary *)previousValue
2534
+ attributeDataValue:(NSDictionary *)attributeDataValue
2535
+ {
2536
+ os_unfair_lock_assert_owner(&self->_lock);
2537
+
2538
+ if (_persistedClusters == nil || _persistedClusterData == nil || !previousValue.count)
2539
+ {
2540
+ return;
2541
+ }
2542
+ // Check if parts list changed or server list changed for the descriptor cluster or the attribute list changed for a cluster.
2543
+ // If yes, we might need to prune any deleted endpoints, clusters or attributes from the storage and persisted cluster data.
2544
+ if (attributePath.cluster.unsignedLongValue == MTRClusterIDTypeDescriptorID) {
2545
+ switch (attributePath.attribute.unsignedLongValue) {
2546
+
2547
+ // If the parts list changed and one or more endpoints were removed, remove all the clusters in _persistedClusters and _persistedClusterData for all those endpoints.
2548
+ // Also remove it from the data store.
2549
+ case MTRAttributeIDTypeClusterDescriptorAttributePartsListID:
2550
+ {
2551
+ NSMutableSet * toBeRemovedEndpoints = [NSMutableSet setWithArray:[self arrayOfNumbersFromAttributeValue:previousValue]];
2552
+ NSSet * endpointsOnDevice = [NSSet setWithArray:[self arrayOfNumbersFromAttributeValue:attributeDataValue]];
2553
+ [toBeRemovedEndpoints minusSet:endpointsOnDevice];
2554
+
2555
+ for (NSNumber * endpoint in toBeRemovedEndpoints)
2556
+ {
2557
+ NSMutableSet<MTRClusterPath *> * clusterPathsToRemove = [[NSMutableSet alloc]init];
2558
+ for (MTRClusterPath * path in _persistedClusters)
2559
+ {
2560
+ if ([path.endpoint isEqualToNumber:endpoint])
2561
+ {
2562
+ [clusterPathsToRemove addObject:path];
2563
+ [_persistedClusterData removeObjectForKey:path];
2564
+ [self.deviceController.controllerDataStore clearStoredClusterDataForNodeIDWithEndpointID:self.nodeID endpointID:endpoint];
2565
+ }
2566
+ }
2567
+ [_persistedClusters minusSet:clusterPathsToRemove];
2568
+ }
2569
+ break;
2570
+ }
2571
+
2572
+ // If the server list changed and clusters were removed, remove the clusters from the _persistedClusters and _persistedClusterData for that endpoint
2573
+ // Also remove it from the data store.
2574
+ case MTRAttributeIDTypeClusterDescriptorAttributeServerListID:
2575
+ {
2576
+ NSMutableSet * toBeRemovedClusters= [NSMutableSet setWithArray:[self arrayOfNumbersFromAttributeValue:previousValue]];
2577
+ NSSet * clustersOnDevice = [NSSet setWithArray:[self arrayOfNumbersFromAttributeValue:attributeDataValue]];
2578
+ [toBeRemovedClusters minusSet:clustersOnDevice];
2579
+
2580
+ NSMutableSet<MTRClusterPath *> * clusterPathsToRemove = [[NSMutableSet alloc]init];
2581
+ for (NSNumber * cluster in toBeRemovedClusters)
2582
+ {
2583
+ for (MTRClusterPath * path in _persistedClusters)
2584
+ {
2585
+ if ([path.endpoint isEqualToNumber:attributePath.endpoint] && [path.cluster isEqualToNumber:cluster])
2586
+ {
2587
+ [clusterPathsToRemove addObject:path];
2588
+ [_persistedClusterData removeObjectForKey:path];
2589
+
2590
+ [self.deviceController.controllerDataStore clearStoredClusterDataForNodeIDWithClusterID:self.nodeID endpointID:path.endpoint clusterID:path.cluster];
2591
+ }
2592
+ }
2593
+ }
2594
+ [_persistedClusters minusSet:clusterPathsToRemove];
2595
+ break;
2596
+ }
2597
+ }
2598
+ }
2599
+
2600
+ switch (attributePath.attribute.unsignedLongValue) {
2601
+ // If the attribute list changed and attributes were removed, remove the attributes from the _persistedClusterData for that cluster and endpoint.
2602
+ // Also remove it from the data store cluster data.
2603
+ case MTRAttributeIDTypeGlobalAttributeAttributeListID:
2604
+ {
2605
+ NSMutableSet * toBeRemovedAttributes= [NSMutableSet setWithArray:[self arrayOfNumbersFromAttributeValue:[self _cachedAttributeValueForPath:attributePath]]];
2606
+ NSSet * attributesOnDevice = [NSSet setWithArray:[self arrayOfNumbersFromAttributeValue:attributeDataValue]];
2607
+
2608
+ [toBeRemovedAttributes minusSet:attributesOnDevice];
2609
+ for (NSNumber * attribute in toBeRemovedAttributes)
2610
+ {
2611
+ for (MTRClusterPath * path in _persistedClusters)
2612
+ {
2613
+ if ([path.endpoint isEqualToNumber:attributePath.endpoint] && [path.cluster isEqualToNumber:attributePath.cluster])
2614
+ {
2615
+ MTRDeviceClusterData * clusterData = [self _clusterDataForPath:path];
2616
+ if (clusterData == nil)
2617
+ {
2618
+ return;
2619
+ }
2620
+ [clusterData _removeValueForAttribute:attribute];
2621
+ [self->_persistedClusterData setObject:clusterData forKey:path];
2622
+
2623
+ NSDictionary<MTRClusterPath *, MTRDeviceClusterData *> * dataStoreClusterData = [self.deviceController.controllerDataStore getStoredClusterDataForNodeID:self.nodeID];
2624
+ NSMutableDictionary<MTRClusterPath *, MTRDeviceClusterData *> * dataStoreClusterDataCopy = [dataStoreClusterData mutableCopy];
2625
+ for (MTRClusterPath * dataStorePath in dataStoreClusterData)
2626
+ {
2627
+ if ([dataStorePath isEqualTo:path])
2628
+ {
2629
+ [dataStoreClusterDataCopy removeObjectForKey:path];
2630
+ [dataStoreClusterDataCopy setObject:clusterData forKey:path];
2631
+ [self.deviceController.controllerDataStore storeClusterData:dataStoreClusterDataCopy forNodeID:self.nodeID];
2632
+ dataStoreClusterData = [NSMutableDictionary dictionaryWithDictionary:[self.deviceController.controllerDataStore getStoredClusterDataForNodeID:self.nodeID]];
2633
+ }
2634
+ }
2635
+ [self _removeCachedAttributeValue:path forPath:attributePath];
2636
+ }
2637
+ }
2638
+ }
2639
+ break;
2640
+ }
2641
+ }
2642
+ }
2643
+
2496
2644
// assume lock is held
2497
2645
- (NSArray *)_getAttributesToReportWithReportedValues:(NSArray<NSDictionary<NSString *, id> *> *)reportedAttributeValues
2498
2646
{
@@ -2544,13 +2692,19 @@ - (NSArray *)_getAttributesToReportWithReportedValues:(NSArray<NSDictionary<NSSt
2544
2692
BOOL readCacheValueChanged = ![self _attributeDataValue:attributeDataValue isEqualToDataValue:previousValue];
2545
2693
// Now that we have grabbed previousValue, update our cache with the attribute value.
2546
2694
if (readCacheValueChanged) {
2547
- [self _setCachedAttributeValue: attributeDataValue forPath: attributePath];
2695
+ if ([self _needsPruningOfEndpointsAndClusters:attributePath])
2696
+ {
2697
+ previousValue = [self _dataValueWithoutDataVersion:previousValue];
2698
+ [self _pruneOrphanedEndpointsAndClusters:attributePath previousValue:previousValue attributeDataValue:attributeDataValue];
2699
+ }
2700
+
2548
2701
if (!_deviceConfigurationChanged) {
2549
2702
_deviceConfigurationChanged = [self _attributeAffectsDeviceConfiguration:attributePath];
2550
2703
if (_deviceConfigurationChanged) {
2551
2704
MTR_LOG_INFO("Device configuration changed due to changes in attribute %@", attributePath);
2552
2705
}
2553
2706
}
2707
+ [self _setCachedAttributeValue:attributeDataValue forPath:attributePath];
2554
2708
}
2555
2709
2556
2710
#ifdef DEBUG
@@ -2660,6 +2814,47 @@ - (void)setPersistedClusterData:(NSDictionary<MTRClusterPath *, MTRDeviceCluster
2660
2814
}
2661
2815
}
2662
2816
2817
+ - (void)_removePersistedClusterDataForPath:(MTRClusterPath *)path
2818
+ {
2819
+ os_unfair_lock_assert_owner(&self->_lock);
2820
+ if (_persistedClusters == nil || _persistedClusterData == nil)
2821
+ {
2822
+ return;
2823
+ }
2824
+
2825
+ [_persistedClusterData removeObjectForKey:path];
2826
+ [_persistedClusters removeObject:path];
2827
+ }
2828
+
2829
+ - (NSMutableSet<MTRClusterPath *> *)_getPersistedClusters
2830
+ {
2831
+ std::lock_guard lock(_lock);
2832
+
2833
+ return _persistedClusters;
2834
+ }
2835
+
2836
+ - (MTRDeviceClusterData *)_getPersistedClusterDataForPath:(MTRClusterPath *)path
2837
+ {
2838
+ std::lock_guard lock(_lock);
2839
+
2840
+ if ([_persistedClusters containsObject:path])
2841
+ {
2842
+ return [_persistedClusterData objectForKey:path];
2843
+ }
2844
+ return nil;
2845
+ }
2846
+
2847
+ - (BOOL)_persistedClusterContains:(MTRClusterPath *)path
2848
+ {
2849
+ std::lock_guard lock(_lock);
2850
+
2851
+ if ([_persistedClusters containsObject:path])
2852
+ {
2853
+ return YES;
2854
+ }
2855
+ return NO;
2856
+ }
2857
+
2663
2858
- (BOOL)deviceCachePrimed
2664
2859
{
2665
2860
std::lock_guard lock(_lock);
0 commit comments