@@ -220,6 +220,11 @@ - (void)storeValue:(MTRDeviceDataValueDictionary _Nullable)value forAttribute:(N
220
220
_attributes[attribute] = value;
221
221
}
222
222
223
+ - (void )_removeValueForAttribute : (NSNumber *)attribute
224
+ {
225
+ [_attributes removeObjectForKey: attribute];
226
+ }
227
+
223
228
- (NSDictionary <NSNumber *, MTRDeviceDataValueDictionary> *)attributes
224
229
{
225
230
return _attributes;
@@ -1609,6 +1614,18 @@ - (void)_setCachedAttributeValue:(MTRDeviceDataValueDictionary _Nullable)value f
1609
1614
_clusterDataToPersist[clusterPath] = clusterData;
1610
1615
}
1611
1616
1617
+ - (void )_removeCachedAttributeValue : (MTRClusterPath *)clusterPath forPath : (MTRAttributePath *)attributePath
1618
+ {
1619
+ os_unfair_lock_assert_owner (&self->_lock );
1620
+
1621
+ if (_clusterDataToPersist == nil ) {
1622
+ return ;
1623
+ }
1624
+ auto * clusterData = [_clusterDataToPersist objectForKey: clusterPath];
1625
+ [clusterData _removeValueForAttribute: attributePath.attribute];
1626
+ [_clusterDataToPersist setObject: clusterData forKey: clusterPath];
1627
+ }
1628
+
1612
1629
- (void )_createDataVersionFilterListFromDictionary : (NSDictionary <MTRClusterPath *, NSNumber *> *)dataVersions dataVersionFilterList : (DataVersionFilter **)dataVersionFilterList count : (size_t *)count sizeReduction : (size_t )sizeReduction
1613
1630
{
1614
1631
size_t maxDataVersionFilterSize = dataVersions.count ;
@@ -2666,7 +2683,7 @@ - (BOOL)_attributeDataValue:(NSDictionary *)one isEqualToDataValue:(NSDictionary
2666
2683
}
2667
2684
2668
2685
// Utility to return data value dictionary without data version
2669
- - (NSDictionary *)_dataValueWithoutDataVersion : (NSDictionary *)attributeValue ;
2686
+ - (NSDictionary *)_dataValueWithoutDataVersion : (NSDictionary *)attributeValue
2670
2687
{
2671
2688
// Sanity check for nil - return the same input to fail gracefully
2672
2689
if (!attributeValue || !attributeValue[MTRTypeKey]) {
@@ -2728,6 +2745,137 @@ - (BOOL)_attributeAffectsDeviceConfiguration:(MTRAttributePath *)attributePath
2728
2745
return NO ;
2729
2746
}
2730
2747
2748
+ - (BOOL )_needsPruningOfEndpointsAndClusters : (MTRAttributePath *)attributePath
2749
+ {
2750
+ // Check for attributes in the descriptor cluster that could cause removal of endpoints and clusters.
2751
+ if (attributePath.cluster .unsignedLongValue == MTRClusterIDTypeDescriptorID) {
2752
+ switch (attributePath.attribute .unsignedLongValue ) {
2753
+ case MTRAttributeIDTypeClusterDescriptorAttributePartsListID:
2754
+ case MTRAttributeIDTypeClusterDescriptorAttributeServerListID:
2755
+ return YES ;
2756
+ }
2757
+ }
2758
+
2759
+ // Check for global attribute - attribute list that could cause removal of attributes.
2760
+ switch (attributePath.attribute .unsignedLongValue ) {
2761
+ case MTRAttributeIDTypeGlobalAttributeAttributeListID:
2762
+ return YES ;
2763
+ }
2764
+ return NO ;
2765
+ }
2766
+
2767
+ - (void )_pruneOrphanedEndpointsAndClusters : (MTRAttributePath *)attributePath
2768
+ previousValue : (NSDictionary *)previousValue
2769
+ attributeDataValue : (NSDictionary *)attributeDataValue
2770
+ {
2771
+ os_unfair_lock_assert_owner (&self->_lock );
2772
+
2773
+ if (_persistedClusters == nil || _persistedClusterData == nil || !previousValue.count )
2774
+ {
2775
+ return ;
2776
+ }
2777
+ // Check if parts list changed or server list changed for the descriptor cluster or the attribute list changed for a cluster.
2778
+ // If yes, we might need to prune any deleted endpoints, clusters or attributes from the storage and persisted cluster data.
2779
+ if (attributePath.cluster .unsignedLongValue == MTRClusterIDTypeDescriptorID) {
2780
+ switch (attributePath.attribute .unsignedLongValue ) {
2781
+
2782
+ // If the parts list changed and one or more endpoints were removed, remove all the clusters in _persistedClusters and _persistedClusterData for all those endpoints.
2783
+ // Also remove it from the data store.
2784
+ case MTRAttributeIDTypeClusterDescriptorAttributePartsListID:
2785
+ {
2786
+ NSMutableSet * toBeRemovedEndpoints = [NSMutableSet setWithArray: [self arrayOfNumbersFromAttributeValue: previousValue]];
2787
+ NSSet * endpointsOnDevice = [NSSet setWithArray: [self arrayOfNumbersFromAttributeValue: attributeDataValue]];
2788
+ [toBeRemovedEndpoints minusSet: endpointsOnDevice];
2789
+
2790
+ for (NSNumber * endpoint in toBeRemovedEndpoints)
2791
+ {
2792
+ NSMutableSet <MTRClusterPath *> * clusterPathsToRemove = [[NSMutableSet alloc ]init];
2793
+ for (MTRClusterPath * path in _persistedClusters)
2794
+ {
2795
+ if ([path.endpoint isEqualToNumber: endpoint])
2796
+ {
2797
+ [clusterPathsToRemove addObject: path];
2798
+ [_persistedClusterData removeObjectForKey: path];
2799
+ [self .deviceController.controllerDataStore clearStoredClusterDataForNodeIDWithEndpointID: self .nodeID endpointID: endpoint];
2800
+ }
2801
+ }
2802
+ [_persistedClusters minusSet: clusterPathsToRemove];
2803
+ }
2804
+ break ;
2805
+ }
2806
+
2807
+ // If the server list changed and clusters were removed, remove the clusters from the _persistedClusters and _persistedClusterData for that endpoint
2808
+ // Also remove it from the data store.
2809
+ case MTRAttributeIDTypeClusterDescriptorAttributeServerListID:
2810
+ {
2811
+ NSMutableSet * toBeRemovedClusters= [NSMutableSet setWithArray: [self arrayOfNumbersFromAttributeValue: previousValue]];
2812
+ NSSet * clustersOnDevice = [NSSet setWithArray: [self arrayOfNumbersFromAttributeValue: attributeDataValue]];
2813
+ [toBeRemovedClusters minusSet: clustersOnDevice];
2814
+
2815
+ NSMutableSet <MTRClusterPath *> * clusterPathsToRemove = [[NSMutableSet alloc ]init];
2816
+ for (NSNumber * cluster in toBeRemovedClusters)
2817
+ {
2818
+ for (MTRClusterPath * path in _persistedClusters)
2819
+ {
2820
+ if ([path.endpoint isEqualToNumber: attributePath.endpoint] && [path.cluster isEqualToNumber: cluster])
2821
+ {
2822
+ [clusterPathsToRemove addObject: path];
2823
+ [_persistedClusterData removeObjectForKey: path];
2824
+
2825
+ [self .deviceController.controllerDataStore clearStoredClusterDataForNodeIDWithClusterID: self .nodeID endpointID: path.endpoint clusterID: path.cluster];
2826
+ }
2827
+ }
2828
+ }
2829
+ [_persistedClusters minusSet: clusterPathsToRemove];
2830
+ break ;
2831
+ }
2832
+ }
2833
+ }
2834
+
2835
+ switch (attributePath.attribute .unsignedLongValue ) {
2836
+ // If the attribute list changed and attributes were removed, remove the attributes from the _persistedClusterData for that cluster and endpoint.
2837
+ // Also remove it from the data store cluster data.
2838
+ case MTRAttributeIDTypeGlobalAttributeAttributeListID:
2839
+ {
2840
+ NSMutableSet * toBeRemovedAttributes= [NSMutableSet setWithArray: [self arrayOfNumbersFromAttributeValue: [self _cachedAttributeValueForPath: attributePath]]];
2841
+ NSSet * attributesOnDevice = [NSSet setWithArray: [self arrayOfNumbersFromAttributeValue: attributeDataValue]];
2842
+
2843
+ [toBeRemovedAttributes minusSet: attributesOnDevice];
2844
+ for (NSNumber * attribute in toBeRemovedAttributes)
2845
+ {
2846
+ for (MTRClusterPath * path in _persistedClusters)
2847
+ {
2848
+ if ([path.endpoint isEqualToNumber: attributePath.endpoint] && [path.cluster isEqualToNumber: attributePath.cluster])
2849
+ {
2850
+ MTRDeviceClusterData * clusterData = [self _clusterDataForPath: path];
2851
+ if (clusterData == nil )
2852
+ {
2853
+ return ;
2854
+ }
2855
+ [clusterData _removeValueForAttribute: attribute];
2856
+ [self ->_persistedClusterData setObject: clusterData forKey: path];
2857
+
2858
+ NSDictionary <MTRClusterPath *, MTRDeviceClusterData *> * dataStoreClusterData = [self .deviceController.controllerDataStore getStoredClusterDataForNodeID: self .nodeID];
2859
+ NSMutableDictionary <MTRClusterPath *, MTRDeviceClusterData *> * dataStoreClusterDataCopy = [dataStoreClusterData mutableCopy ];
2860
+ for (MTRClusterPath * dataStorePath in dataStoreClusterData)
2861
+ {
2862
+ if ([dataStorePath isEqualTo: path])
2863
+ {
2864
+ [dataStoreClusterDataCopy removeObjectForKey: path];
2865
+ [dataStoreClusterDataCopy setObject: clusterData forKey: path];
2866
+ [self .deviceController.controllerDataStore storeClusterData: dataStoreClusterDataCopy forNodeID: self .nodeID];
2867
+ dataStoreClusterData = [NSMutableDictionary dictionaryWithDictionary: [self .deviceController.controllerDataStore getStoredClusterDataForNodeID: self .nodeID]];
2868
+ }
2869
+ }
2870
+ [self _removeCachedAttributeValue: path forPath: attributePath];
2871
+ }
2872
+ }
2873
+ }
2874
+ break ;
2875
+ }
2876
+ }
2877
+ }
2878
+
2731
2879
// assume lock is held
2732
2880
- (NSArray *)_getAttributesToReportWithReportedValues : (NSArray <NSDictionary<NSString *, id> *> *)reportedAttributeValues fromSubscription : (BOOL )isFromSubscription
2733
2881
{
@@ -2781,13 +2929,20 @@ - (NSArray *)_getAttributesToReportWithReportedValues:(NSArray<NSDictionary<NSSt
2781
2929
[self _noteDataVersion: dataVersion forClusterPath: clusterPath];
2782
2930
}
2783
2931
2784
- [self _setCachedAttributeValue: attributeDataValue forPath: attributePath fromSubscription: isFromSubscription];
2932
+ if ([self _needsPruningOfEndpointsAndClusters: attributePath])
2933
+ {
2934
+ previousValue = [self _dataValueWithoutDataVersion: previousValue];
2935
+ [self _pruneOrphanedEndpointsAndClusters: attributePath previousValue: previousValue attributeDataValue: attributeDataValue];
2936
+ }
2937
+
2785
2938
if (!_deviceConfigurationChanged) {
2786
2939
_deviceConfigurationChanged = [self _attributeAffectsDeviceConfiguration: attributePath];
2787
2940
if (_deviceConfigurationChanged) {
2788
2941
MTR_LOG_INFO (" Device configuration changed due to changes in attribute %@" , attributePath);
2789
2942
}
2790
2943
}
2944
+
2945
+ [self _setCachedAttributeValue: attributeDataValue forPath: attributePath fromSubscription: isFromSubscription];
2791
2946
}
2792
2947
2793
2948
#ifdef DEBUG
@@ -2941,6 +3096,47 @@ - (void)_storePersistedDeviceData
2941
3096
[datastore storeDeviceData: [data copy ] forNodeID: self .nodeID];
2942
3097
}
2943
3098
3099
+ - (void )_removePersistedClusterDataForPath : (MTRClusterPath *)path
3100
+ {
3101
+ os_unfair_lock_assert_owner (&self->_lock );
3102
+ if (_persistedClusters == nil || _persistedClusterData == nil )
3103
+ {
3104
+ return ;
3105
+ }
3106
+
3107
+ [_persistedClusterData removeObjectForKey: path];
3108
+ [_persistedClusters removeObject: path];
3109
+ }
3110
+
3111
+ - (NSMutableSet <MTRClusterPath *> *)_getPersistedClusters
3112
+ {
3113
+ std::lock_guard lock (_lock);
3114
+
3115
+ return _persistedClusters;
3116
+ }
3117
+
3118
+ - (MTRDeviceClusterData *)_getPersistedClusterDataForPath : (MTRClusterPath *)path
3119
+ {
3120
+ std::lock_guard lock (_lock);
3121
+
3122
+ if ([_persistedClusters containsObject: path])
3123
+ {
3124
+ return [_persistedClusterData objectForKey: path];
3125
+ }
3126
+ return nil ;
3127
+ }
3128
+
3129
+ - (BOOL )_persistedClusterContains : (MTRClusterPath *)path
3130
+ {
3131
+ std::lock_guard lock (_lock);
3132
+
3133
+ if ([_persistedClusters containsObject: path])
3134
+ {
3135
+ return YES ;
3136
+ }
3137
+ return NO ;
3138
+ }
3139
+
2944
3140
- (BOOL )deviceCachePrimed
2945
3141
{
2946
3142
std::lock_guard lock (_lock);
0 commit comments