@@ -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