Skip to content

Commit

Permalink
[Darwin] MTRDevice should persist data version by cluster
Browse files Browse the repository at this point in the history
  • Loading branch information
jtung-apple committed Mar 21, 2024
1 parent 015340a commit cbe290e
Show file tree
Hide file tree
Showing 7 changed files with 326 additions and 17 deletions.
116 changes: 102 additions & 14 deletions src/darwin/Framework/CHIP/MTRDevice.mm
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,43 @@ typedef NS_ENUM(NSUInteger, MTRDeviceWorkItemDuplicateTypeID) {
MTRDeviceWorkItemDuplicateReadTypeID = 1,
};

@implementation MTRDeviceClusterData

static NSString * const sDataVersionKey = @"dataVersion";

+ (BOOL)supportsSecureCoding
{
return YES;
}

- (NSString *)description
{
return [NSString stringWithFormat:@"<MTRDeviceClusterData: dataVersion %@>", _dataVersion];
}

- (nullable instancetype)initWithCoder:(NSCoder *)decoder
{
self = [super init];
if (self == nil) {
return nil;
}

_dataVersion = [decoder decodeObjectOfClass:[NSNumber class] forKey:sDataVersionKey];
if (_dataVersion != nil && ![_dataVersion isKindOfClass:[NSNumber class]]) {
MTR_LOG_ERROR("MTRDeviceClusterData got %@ for data version, not NSNumber.", _dataVersion);
return nil;
}

return self;
}

- (void)encodeWithCoder:(NSCoder *)coder
{
[coder encodeObject:self.dataVersion forKey:sDataVersionKey];
}

@end

@interface MTRDevice ()
@property (nonatomic, readonly) os_unfair_lock lock; // protects the caches and device state
// protects against concurrent time updates by guarding timeUpdateScheduled flag which manages time updates scheduling,
Expand Down Expand Up @@ -228,6 +265,8 @@ @implementation MTRDevice {
NSUInteger _unitTestAttributesReportedSinceLastCheck;
#endif
BOOL _delegateDeviceCachePrimedCalled;
NSMutableDictionary<MTRClusterPath *, MTRDeviceClusterData *> * _clusterData;
NSMutableDictionary<MTRClusterPath *, MTRDeviceClusterData *> * _clusterDataToPersist;
}

- (instancetype)initWithNodeID:(NSNumber *)nodeID controller:(MTRDeviceController *)controller
Expand All @@ -244,6 +283,7 @@ - (instancetype)initWithNodeID:(NSNumber *)nodeID controller:(MTRDeviceControlle
_expectedValueCache = [NSMutableDictionary dictionary];
_asyncWorkQueue = [[MTRAsyncWorkQueue alloc] initWithContext:self];
_state = MTRDeviceStateUnknown;
_clusterData = [NSMutableDictionary dictionary];
MTR_LOG_INFO("%@ init with hex nodeID 0x%016llX", self, _nodeID.unsignedLongLongValue);
}
return self;
Expand Down Expand Up @@ -777,6 +817,13 @@ - (void)_handleReportEnd
_receivingPrimingReport = NO;
_estimatedStartTimeFromGeneralDiagnosticsUpTime = nil;

BOOL dataStoreExists = _deviceController.controllerDataStore != nil;
if (dataStoreExists && _clusterDataToPersist.count) {
MTR_LOG_DEFAULT("%@ Storing cluster information (data version) count: %lu", self, static_cast<unsigned long>(_clusterDataToPersist.count));
[_deviceController.controllerDataStore storeClusterData:_clusterDataToPersist forNodeID:_nodeID];
_clusterDataToPersist = nil;
}

// For unit testing only
#ifdef DEBUG
id delegate = _weakDelegate.strongObject;
Expand Down Expand Up @@ -924,18 +971,10 @@ - (void)_handleEventReport:(NSArray<NSDictionary<NSString *, id> *> *)eventRepor
NSMutableDictionary<MTRClusterPath *, NSNumber *> * dataVersions = [NSMutableDictionary dictionary];
std::lock_guard lock(_lock);

for (MTRAttributePath * path in _readCache) {
NSDictionary * dataValue = _readCache[path];
NSNumber * dataVersionNumber = dataValue[MTRDataVersionKey];
if (dataVersionNumber) {
MTRClusterPath * clusterPath = [MTRClusterPath clusterPathWithEndpointID:path.endpoint clusterID:path.cluster];
NSNumber * currentDataVersion = dataVersions[clusterPath];
// Use the highest data version
if (currentDataVersion.unsignedLongValue < dataVersionNumber.unsignedLongValue) {
dataVersions[clusterPath] = dataVersionNumber;
}
}
for (MTRClusterPath * path in _clusterData) {
dataVersions[path] = _clusterData[path].dataVersion;
}

MTR_LOG_INFO("%@ _getCachedDataVersions dataVersions count: %lu readCache count: %lu", self, static_cast<unsigned long>(dataVersions.count), static_cast<unsigned long>(_readCache.count));

return dataVersions;
Expand Down Expand Up @@ -1873,9 +1912,45 @@ - (void)_performScheduledExpirationCheck

- (BOOL)_attributeDataValue:(NSDictionary *)one isEqualToDataValue:(NSDictionary *)theOther
{
// Attribute data-value dictionary should be all standard containers which
// means isEqual: comparisons all the way down, making a deep comparison.
return [one isEqualToDictionary:theOther];
// Attribute data-value dictionaries are equal if type and value are equal
return [one[MTRTypeKey] isEqual:theOther[MTRTypeKey]] && (!one[MTRValueKey] || [one[MTRValueKey] isEqual:theOther[MTRValueKey]]);
}

// Utility to return data value dictionary without data version
- (NSDictionary *)_dataValueWithoutDataVersion:(NSDictionary *)attributeValue;
{
if (attributeValue[MTRValueKey]) {
return @{ MTRTypeKey : attributeValue[MTRTypeKey], MTRValueKey : attributeValue[MTRValueKey] };
} else {
return @{ MTRTypeKey : attributeValue[MTRTypeKey] };
}
}

// Update cluster data version and also note the change, so at onReportEnd it can be persisted
- (void)_updateDataVersion:(NSNumber *)dataVersion forClusterPath:(MTRClusterPath *)clusterPath
{
BOOL dataVersionChanged = NO;
MTRDeviceClusterData * clusterData = _clusterData[clusterPath];
if (!clusterData) {
clusterData = [[MTRDeviceClusterData alloc] init];
_clusterData[clusterPath] = clusterData;
dataVersionChanged = YES;
} else if (![clusterData.dataVersion isEqualToNumber:dataVersion]) {
dataVersionChanged = YES;
}

if (dataVersionChanged) {
clusterData.dataVersion = dataVersion;

// Set up for persisting if there is data store
BOOL dataStoreExists = _deviceController.controllerDataStore != nil;
if (dataStoreExists) {
if (!_clusterDataToPersist) {
_clusterDataToPersist = [NSMutableDictionary dictionary];
}
_clusterDataToPersist[clusterPath] = clusterData;
}
}
}

// assume lock is held
Expand Down Expand Up @@ -1919,6 +1994,14 @@ - (NSArray *)_getAttributesToReportWithReportedValues:(NSArray<NSDictionary<NSSt
previousValue = _readCache[attributePath];
_readCache[attributePath] = nil;
} else {
// First separate data version and restore data value to a form without data version
NSNumber * dataVersion = attributeDataValue[MTRDataVersionKey];
attributeDataValue = [self _dataValueWithoutDataVersion:attributeDataValue];
if (dataVersion) {
MTRClusterPath * clusterPath = attributePath;
[self _updateDataVersion:dataVersion forClusterPath:clusterPath];
}

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) {
Expand Down Expand Up @@ -2013,6 +2096,11 @@ - (void)setAttributeValues:(NSArray<NSDictionary *> *)attributeValues reportChan
}
}

- (void)setClusterData:(NSDictionary<MTRClusterPath *, MTRDeviceClusterData *> *)clusterData
{
[_clusterData addEntriesFromDictionary:clusterData];
}

- (BOOL)deviceCachePrimed
{
std::lock_guard lock(_lock);
Expand Down
5 changes: 5 additions & 0 deletions src/darwin/Framework/CHIP/MTRDeviceController.mm
Original file line number Diff line number Diff line change
Expand Up @@ -935,6 +935,11 @@ - (MTRDevice *)deviceForNodeID:(NSNumber *)nodeID
if (attributesFromCache.count) {
[deviceToReturn setAttributeValues:attributesFromCache reportChanges:NO];
}
NSDictionary * clusterData = [_controllerDataStore getStoredClusterDataForNodeID:nodeID];
MTR_LOG_INFO("Loaded %lu cluster data from storage for %@", static_cast<unsigned long>(clusterData.count), deviceToReturn);
if (clusterData.count) {
[deviceToReturn setClusterData:clusterData];
}
}

return deviceToReturn;
Expand Down
3 changes: 3 additions & 0 deletions src/darwin/Framework/CHIP/MTRDeviceControllerDataStore.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#import <Foundation/Foundation.h>
#import <Matter/MTRDefines.h>
#import <Matter/MTRDeviceController.h>
#import <Matter/MTRDevice_Internal.h>
#if MTR_PER_CONTROLLER_STORAGE_ENABLED
#import <Matter/MTRDeviceControllerStorageDelegate.h>
#else
Expand Down Expand Up @@ -67,7 +68,9 @@ NS_ASSUME_NONNULL_BEGIN
* 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;
- (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;

Expand Down
Loading

0 comments on commit cbe290e

Please sign in to comment.