@@ -176,6 +176,7 @@ typedef NS_ENUM(NSUInteger, MTRDeviceWorkItemDuplicateTypeID) {
176
176
@implementation MTRDeviceClusterData
177
177
178
178
static NSString * const sDataVersionKey = @" dataVersion" ;
179
+ static NSString * const sAttributesKey = @" attributes" ;
179
180
180
181
+ (BOOL )supportsSecureCoding
181
182
{
@@ -184,7 +185,20 @@ + (BOOL)supportsSecureCoding
184
185
185
186
- (NSString *)description
186
187
{
187
- return [NSString stringWithFormat: @" <MTRDeviceClusterData: dataVersion %@ >" , _dataVersion];
188
+ return [NSString stringWithFormat: @" <MTRDeviceClusterData: dataVersion %@ attributes count %lu >" , _dataVersion, static_cast <unsigned long >(_attributes.count)];
189
+ }
190
+
191
+ - (nullable instancetype )initWithDataVersion : (NSNumber * _Nullable)dataVersion attributes : (NSDictionary <NSNumber *, MTRDeviceDataValueDictionary> * _Nullable)attributes
192
+ {
193
+ self = [super init ];
194
+ if (self == nil ) {
195
+ return nil ;
196
+ }
197
+
198
+ _dataVersion = [dataVersion copy ];
199
+ _attributes = [attributes copy ];
200
+
201
+ return self;
188
202
}
189
203
190
204
- (nullable instancetype )initWithCoder : (NSCoder *)decoder
@@ -200,12 +214,25 @@ - (nullable instancetype)initWithCoder:(NSCoder *)decoder
200
214
return nil ;
201
215
}
202
216
217
+ static NSSet * const sAttributeValueClasses = [NSSet setWithObjects: [NSDictionary class ], [NSArray class ], [NSData class ], [NSString class ], [NSNumber class ], nil ];
218
+ _attributes = [decoder decodeObjectOfClasses: sAttributeValueClasses forKey: sAttributesKey ];
219
+ if (_attributes != nil && ![_attributes isKindOfClass: [NSDictionary class ]]) {
220
+ MTR_LOG_ERROR (" MTRDeviceClusterData got %@ for attributes, not NSDictionary." , _attributes);
221
+ return nil ;
222
+ }
223
+
203
224
return self;
204
225
}
205
226
206
227
- (void )encodeWithCoder : (NSCoder *)coder
207
228
{
208
229
[coder encodeObject: self .dataVersion forKey: sDataVersionKey ];
230
+ [coder encodeObject: self .attributes forKey: sAttributesKey ];
231
+ }
232
+
233
+ - (id )copyWithZone : (NSZone *)zone
234
+ {
235
+ return [[MTRDeviceClusterData alloc ] initWithDataVersion: _dataVersion attributes: _attributes];
209
236
}
210
237
211
238
@end
@@ -285,8 +312,11 @@ @implementation MTRDevice {
285
312
NSUInteger _unitTestAttributesReportedSinceLastCheck;
286
313
#endif
287
314
BOOL _delegateDeviceCachePrimedCalled;
315
+
316
+ // With MTRDeviceClusterData now able to hold attribute data, the plan is to move to using it
317
+ // as the read cache, should testing prove attribute storage by cluster is the better solution.
288
318
NSMutableDictionary <MTRClusterPath *, MTRDeviceClusterData *> * _clusterData;
289
- NSMutableDictionary <MTRClusterPath *, MTRDeviceClusterData * > * _clusterDataToPersist ;
319
+ NSMutableSet <MTRClusterPath *> * _clustersToPersist ;
290
320
}
291
321
292
322
- (instancetype )initWithNodeID : (NSNumber *)nodeID controller : (MTRDeviceController *)controller
@@ -836,6 +866,34 @@ - (void)_handleReportBegin
836
866
}
837
867
}
838
868
869
+ - (NSDictionary <NSNumber *, MTRDeviceDataValueDictionary> *)_attributesForCluster : (MTRClusterPath *)clusterPath
870
+ {
871
+ os_unfair_lock_assert_owner (&self->_lock );
872
+ NSMutableDictionary * attributesToReturn = [NSMutableDictionary dictionary ];
873
+ for (MTRAttributePath * attributePath in _readCache) {
874
+ if ([attributePath.endpoint isEqualToNumber: clusterPath.endpoint] && [attributePath.cluster isEqualToNumber: clusterPath.cluster]) {
875
+ attributesToReturn[attributePath.attribute] = _readCache[attributePath];
876
+ }
877
+ }
878
+ return attributesToReturn;
879
+ }
880
+
881
+ - (NSDictionary <MTRClusterPath *, MTRDeviceClusterData *> *)_clusterDataForPaths : (NSSet <MTRClusterPath *> *)clusterPaths
882
+ {
883
+ os_unfair_lock_assert_owner (&self->_lock );
884
+ NSMutableDictionary * clusterDataToReturn = [NSMutableDictionary dictionary ];
885
+ for (MTRClusterPath * clusterPath in clusterPaths) {
886
+ NSNumber * dataVersion = _clusterData[clusterPath].dataVersion ;
887
+ NSDictionary <NSNumber *, MTRDeviceDataValueDictionary> * attributes = [self _attributesForCluster: clusterPath];
888
+ if (dataVersion || attributes) {
889
+ MTRDeviceClusterData * clusterData = [[MTRDeviceClusterData alloc ] initWithDataVersion: dataVersion attributes: attributes];
890
+ clusterDataToReturn[clusterPath] = clusterData;
891
+ }
892
+ }
893
+
894
+ return clusterDataToReturn;
895
+ }
896
+
839
897
- (void )_handleReportEnd
840
898
{
841
899
std::lock_guard lock (_lock);
@@ -844,10 +902,11 @@ - (void)_handleReportEnd
844
902
_estimatedStartTimeFromGeneralDiagnosticsUpTime = nil ;
845
903
846
904
BOOL dataStoreExists = _deviceController.controllerDataStore != nil ;
847
- if (dataStoreExists && _clusterDataToPersist.count ) {
848
- MTR_LOG_DEFAULT (" %@ Storing cluster information (data version) count: %lu" , self, static_cast <unsigned long >(_clusterDataToPersist.count ));
849
- [_deviceController.controllerDataStore storeClusterData: _clusterDataToPersist forNodeID: _nodeID];
850
- _clusterDataToPersist = nil ;
905
+ if (dataStoreExists && _clustersToPersist.count ) {
906
+ MTR_LOG_DEFAULT (" %@ Storing cluster information (data version) count: %lu" , self, static_cast <unsigned long >(_clustersToPersist.count ));
907
+ NSDictionary <MTRClusterPath *, MTRDeviceClusterData *> * clusterData = [self _clusterDataForPaths: _clustersToPersist];
908
+ [_deviceController.controllerDataStore storeClusterData: clusterData forNodeID: _nodeID];
909
+ _clustersToPersist = nil ;
851
910
}
852
911
853
912
// For unit testing only
@@ -1939,13 +1998,28 @@ - (void)_performScheduledExpirationCheck
1939
1998
1940
1999
- (BOOL )_attributeDataValue : (NSDictionary *)one isEqualToDataValue : (NSDictionary *)theOther
1941
2000
{
1942
- // Attribute data-value dictionaries are equal if type and value are equal
2001
+ // Sanity check for nil cases
2002
+ if (!one && !theOther) {
2003
+ MTR_LOG_ERROR (" %@ attribute data-value comparison does not expect comparing two nil dictionaries" , self);
2004
+ return YES ;
2005
+ }
2006
+ if (!one || !theOther) {
2007
+ MTR_LOG_ERROR (" %@ attribute data-value comparison does not expect a dictionary to be nil: %@ %@" , self, one, theOther);
2008
+ return NO ;
2009
+ }
2010
+
2011
+ // Attribute data-value dictionaries are equal if type and value are equal, and specifically, this should return true if values are both nil
1943
2012
return [one[MTRTypeKey] isEqual: theOther[MTRTypeKey]] && ((one[MTRValueKey] == theOther[MTRValueKey]) || [one[MTRValueKey] isEqual: theOther[MTRValueKey]]);
1944
2013
}
1945
2014
1946
2015
// Utility to return data value dictionary without data version
1947
2016
- (NSDictionary *)_dataValueWithoutDataVersion : (NSDictionary *)attributeValue ;
1948
2017
{
2018
+ // Sanity check for nil - return the same input to fail gracefully
2019
+ if (!attributeValue || !attributeValue[MTRTypeKey]) {
2020
+ return attributeValue;
2021
+ }
2022
+
1949
2023
if (attributeValue[MTRValueKey]) {
1950
2024
return @{ MTRTypeKey : attributeValue[MTRTypeKey], MTRValueKey : attributeValue[MTRValueKey] };
1951
2025
} else {
@@ -1954,9 +2028,12 @@ - (NSDictionary *)_dataValueWithoutDataVersion:(NSDictionary *)attributeValue;
1954
2028
}
1955
2029
1956
2030
// Update cluster data version and also note the change, so at onReportEnd it can be persisted
1957
- - (void )_updateDataVersion : (NSNumber *)dataVersion forClusterPath : (MTRClusterPath *)clusterPath
2031
+ - (void )_noteDataVersion : (NSNumber *)dataVersion forClusterPath : (MTRClusterPath *)clusterPath
1958
2032
{
2033
+ os_unfair_lock_assert_owner (&self->_lock );
2034
+
1959
2035
BOOL dataVersionChanged = NO ;
2036
+ // Update data version used for subscription filtering
1960
2037
MTRDeviceClusterData * clusterData = _clusterData[clusterPath];
1961
2038
if (!clusterData) {
1962
2039
clusterData = [[MTRDeviceClusterData alloc ] init ];
@@ -1969,17 +2046,25 @@ - (void)_updateDataVersion:(NSNumber *)dataVersion forClusterPath:(MTRClusterPat
1969
2046
if (dataVersionChanged) {
1970
2047
clusterData.dataVersion = dataVersion;
1971
2048
1972
- // Set up for persisting if there is data store
2049
+ // Mark cluster path as needing persistence if needed
1973
2050
BOOL dataStoreExists = _deviceController.controllerDataStore != nil ;
1974
2051
if (dataStoreExists) {
1975
- if (!_clusterDataToPersist) {
1976
- _clusterDataToPersist = [NSMutableDictionary dictionary ];
1977
- }
1978
- _clusterDataToPersist[clusterPath] = clusterData;
2052
+ [self _noteChangeForClusterPath: clusterPath];
1979
2053
}
1980
2054
}
1981
2055
}
1982
2056
2057
+ // Assuming data store exists, note that the cluster should be persisted at onReportEnd
2058
+ - (void )_noteChangeForClusterPath : (MTRClusterPath *)clusterPath
2059
+ {
2060
+ os_unfair_lock_assert_owner (&self->_lock );
2061
+
2062
+ if (!_clustersToPersist) {
2063
+ _clustersToPersist = [NSMutableSet set ];
2064
+ }
2065
+ [_clustersToPersist addObject: clusterPath];
2066
+ }
2067
+
1983
2068
// assume lock is held
1984
2069
- (NSArray *)_getAttributesToReportWithReportedValues : (NSArray <NSDictionary<NSString *, id> *> *)reportedAttributeValues
1985
2070
{
@@ -1988,10 +2073,12 @@ - (NSArray *)_getAttributesToReportWithReportedValues:(NSArray<NSDictionary<NSSt
1988
2073
NSMutableArray * attributesToReport = [NSMutableArray array ];
1989
2074
NSMutableArray * attributePathsToReport = [NSMutableArray array ];
1990
2075
BOOL dataStoreExists = _deviceController.controllerDataStore != nil ;
2076
+ #if !MTRDEVICE_ATTRIBUTE_CACHE_STORE_ATTRIBUTES_BY_CLUSTER
1991
2077
NSMutableArray * attributesToPersist;
1992
2078
if (dataStoreExists) {
1993
2079
attributesToPersist = [NSMutableArray array ];
1994
2080
}
2081
+ #endif
1995
2082
for (NSDictionary <NSString *, id > * attributeResponseValue in reportedAttributeValues) {
1996
2083
MTRAttributePath * attributePath = attributeResponseValue[MTRAttributePathKey];
1997
2084
NSDictionary * attributeDataValue = attributeResponseValue[MTRDataKey];
@@ -2023,9 +2110,9 @@ - (NSArray *)_getAttributesToReportWithReportedValues:(NSArray<NSDictionary<NSSt
2023
2110
} else {
2024
2111
// First separate data version and restore data value to a form without data version
2025
2112
NSNumber * dataVersion = attributeDataValue[MTRDataVersionKey];
2113
+ MTRClusterPath * clusterPath = [MTRClusterPath clusterPathWithEndpointID: attributePath.endpoint clusterID: attributePath.cluster];
2026
2114
if (dataVersion) {
2027
- MTRClusterPath * clusterPath = [MTRClusterPath clusterPathWithEndpointID: attributePath.endpoint clusterID: attributePath.cluster];
2028
- [self _updateDataVersion: dataVersion forClusterPath: clusterPath];
2115
+ [self _noteDataVersion: dataVersion forClusterPath: clusterPath];
2029
2116
2030
2117
// Remove data version from what we cache in memory
2031
2118
attributeDataValue = [self _dataValueWithoutDataVersion: attributeDataValue];
@@ -2034,6 +2121,9 @@ - (NSArray *)_getAttributesToReportWithReportedValues:(NSArray<NSDictionary<NSSt
2034
2121
BOOL readCacheValueChanged = ![self _attributeDataValue: attributeDataValue isEqualToDataValue: _readCache[attributePath]];
2035
2122
// Check if attribute needs to be persisted - compare only to read cache and disregard expected values
2036
2123
if (dataStoreExists && readCacheValueChanged) {
2124
+ #if MTRDEVICE_ATTRIBUTE_CACHE_STORE_ATTRIBUTES_BY_CLUSTER
2125
+ [self _noteChangeForClusterPath: clusterPath];
2126
+ #else
2037
2127
NSDictionary * attributeResponseValueToPersist;
2038
2128
if (dataVersion) {
2039
2129
// Remove data version from what we cache in memory and storage
@@ -2044,6 +2134,7 @@ - (NSArray *)_getAttributesToReportWithReportedValues:(NSArray<NSDictionary<NSSt
2044
2134
attributeResponseValueToPersist = attributeResponseValue;
2045
2135
}
2046
2136
[attributesToPersist addObject: attributeResponseValueToPersist];
2137
+ #endif
2047
2138
}
2048
2139
2049
2140
// if expected values exists, purge and update read cache
@@ -2106,17 +2197,22 @@ - (NSArray *)_getAttributesToReportWithReportedValues:(NSArray<NSDictionary<NSSt
2106
2197
2107
2198
MTR_LOG_INFO (" %@ report from reported values %@" , self, attributePathsToReport);
2108
2199
2200
+ #if !MTRDEVICE_ATTRIBUTE_CACHE_STORE_ATTRIBUTES_BY_CLUSTER
2109
2201
if (dataStoreExists && attributesToPersist.count ) {
2110
2202
[_deviceController.controllerDataStore storeAttributeValues: attributesToPersist forNodeID: _nodeID];
2111
2203
}
2204
+ #endif
2112
2205
2113
2206
return attributesToReport;
2114
2207
}
2115
2208
2116
- - (void )setAttributeValues : (NSArray <NSDictionary *> *)attributeValues reportChanges : (BOOL )reportChanges
2209
+ - (void )_setAttributeValues : (NSArray <NSDictionary *> *)attributeValues reportChanges : (BOOL )reportChanges
2117
2210
{
2118
- MTR_LOG_INFO (" %@ setAttributeValues count: %lu reportChanges: %d" , self, static_cast <unsigned long >(attributeValues.count ), reportChanges);
2119
- std::lock_guard lock (_lock);
2211
+ os_unfair_lock_assert_owner (&self->_lock );
2212
+
2213
+ if (!attributeValues.count ) {
2214
+ return ;
2215
+ }
2120
2216
2121
2217
if (reportChanges) {
2122
2218
[self _reportAttributes: [self _getAttributesToReportWithReportedValues: attributeValues]];
@@ -2134,8 +2230,42 @@ - (void)setAttributeValues:(NSArray<NSDictionary *> *)attributeValues reportChan
2134
2230
}
2135
2231
}
2136
2232
2233
+ - (void )setAttributeValues : (NSArray <NSDictionary *> *)attributeValues reportChanges : (BOOL )reportChanges
2234
+ {
2235
+ MTR_LOG_INFO (" %@ setAttributeValues count: %lu reportChanges: %d" , self, static_cast <unsigned long >(attributeValues.count ), reportChanges);
2236
+ std::lock_guard lock (_lock);
2237
+ [self _setAttributeValues: attributeValues reportChanges: reportChanges];
2238
+ }
2239
+
2137
2240
- (void )setClusterData : (NSDictionary <MTRClusterPath *, MTRDeviceClusterData *> *)clusterData
2138
2241
{
2242
+ MTR_LOG_INFO (" %@ setClusterData count: %lu" , self, static_cast <unsigned long >(clusterData.count ));
2243
+ if (!clusterData.count ) {
2244
+ return ;
2245
+ }
2246
+
2247
+ std::lock_guard lock (_lock);
2248
+
2249
+ #if MTRDEVICE_ATTRIBUTE_CACHE_STORE_ATTRIBUTES_BY_CLUSTER
2250
+ // For each cluster, extract and create the attribute response-value for the read cache
2251
+ // 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
2252
+ for (MTRClusterPath * clusterPath in clusterData) {
2253
+ MTRDeviceClusterData * data = clusterData[clusterPath];
2254
+ // Build and set attributes one cluster at a time to avoid creating a ton of temporary objects at a time
2255
+ @autoreleasepool {
2256
+ NSMutableArray * attributeValues = [NSMutableArray array ];
2257
+ for (NSNumber * attributeID in data.attributes ) {
2258
+ MTRAttributePath * attributePath = [MTRAttributePath attributePathWithEndpointID: clusterPath.endpoint clusterID: clusterPath.cluster attributeID: attributeID];
2259
+ NSDictionary * responseValue = @{ MTRAttributePathKey : attributePath, MTRDataKey : data.attributes [attributeID] };
2260
+ [attributeValues addObject: responseValue];
2261
+ }
2262
+ if (attributeValues.count ) {
2263
+ [self _setAttributeValues: attributeValues reportChanges: NO ];
2264
+ }
2265
+ }
2266
+ }
2267
+ #endif
2268
+
2139
2269
[_clusterData addEntriesFromDictionary: clusterData];
2140
2270
}
2141
2271
0 commit comments