blob: d6c7e8c00dfeb1070a2a63e40873968979283ce0 [file] [log] [blame]
/**
* Copyright (c) 2023 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "MTRDeviceControllerDataStore.h"
// Importing MTRBaseDevice.h for the MTRAttributePath class. Needs to change when https://github.com/project-chip/connectedhomeip/issues/31247 is fixed.
#import "MTRBaseDevice.h"
#import "MTRLogging_Internal.h"
#include <lib/core/CASEAuthTag.h>
#include <lib/core/NodeId.h>
#include <lib/support/SafeInt.h>
// FIXME: Are these good key strings? https://github.com/project-chip/connectedhomeip/issues/28973
static NSString * sResumptionNodeListKey = @"caseResumptionNodeList";
static NSString * sLastLocallyUsedNOCKey = @"lastLocallyUsedControllerNOC";
static NSString * ResumptionByNodeIDKey(NSNumber * nodeID)
{
return [NSString stringWithFormat:@"caseResumptionByNodeID/%llx", nodeID.unsignedLongLongValue];
}
static NSString * ResumptionByResumptionIDKey(NSData * resumptionID)
{
return
[NSString stringWithFormat:@"caseResumptionByResumptionID/%s", [resumptionID base64EncodedStringWithOptions:0].UTF8String];
}
static bool IsUnsignedIntegerNumber(id _Nullable value)
{
if (value == nil) {
return false;
}
if (![value isKindOfClass:[NSNumber class]]) {
return false;
}
NSNumber * number = value;
// Not sure how to check for the number being an integer.
if ([number compare:@(0)] == NSOrderedAscending) {
return false;
}
return true;
}
static bool IsValidNodeIDNumber(id _Nullable value)
{
// Node IDs cannot be negative.
if (!IsUnsignedIntegerNumber(value)) {
return false;
}
NSNumber * number = value;
// Validate that this is a valid operational ID, not some garbage unsigned
// int value that can't be a node id.
uint64_t unsignedValue = number.unsignedLongLongValue;
if (!chip::IsOperationalNodeId(unsignedValue)) {
return false;
}
return true;
}
static bool IsValidCATNumber(id _Nullable value)
{
// CATs cannot be negative.
if (!IsUnsignedIntegerNumber(value)) {
return false;
}
NSNumber * number = value;
// Validate that this is a valid CAT value and, not some garbage unsigned int
// value that can't be a CAT.
uint64_t unsignedValue = number.unsignedLongLongValue;
if (!chip::CanCastTo<chip::CASEAuthTag>(unsignedValue)) {
return false;
}
auto tag = static_cast<chip::CASEAuthTag>(unsignedValue);
if (!chip::IsValidCASEAuthTag(tag)) {
return false;
}
return true;
}
@implementation MTRDeviceControllerDataStore {
id<MTRDeviceControllerStorageDelegate> _storageDelegate;
dispatch_queue_t _storageDelegateQueue;
MTRDeviceController * _controller;
// Array of nodes with resumption info, oldest-stored first.
NSMutableArray<NSNumber *> * _nodesWithResumptionInfo;
}
- (nullable instancetype)initWithController:(MTRDeviceController *)controller
storageDelegate:(id<MTRDeviceControllerStorageDelegate>)storageDelegate
storageDelegateQueue:(dispatch_queue_t)storageDelegateQueue
{
if (!(self = [super init])) {
return nil;
}
_controller = controller;
_storageDelegate = storageDelegate;
_storageDelegateQueue = storageDelegateQueue;
__block id resumptionNodeList;
dispatch_sync(_storageDelegateQueue, ^{
@autoreleasepool {
resumptionNodeList = [_storageDelegate controller:_controller
valueForKey:sResumptionNodeListKey
securityLevel:MTRStorageSecurityLevelSecure
sharingType:MTRStorageSharingTypeNotShared];
}
});
if (resumptionNodeList != nil) {
if (![resumptionNodeList isKindOfClass:[NSArray class]]) {
MTR_LOG_ERROR("List of CASE resumption node IDs is not an array");
return nil;
}
for (id value in resumptionNodeList) {
if (!IsValidNodeIDNumber(value)) {
MTR_LOG_ERROR("Resumption node ID contains invalid value: %@", value);
return nil;
}
}
_nodesWithResumptionInfo = [resumptionNodeList mutableCopy];
} else {
_nodesWithResumptionInfo = [[NSMutableArray alloc] init];
}
return self;
}
- (void)fetchAttributeDataForAllDevices:(MTRDeviceControllerDataStoreClusterDataHandler)clusterDataHandler
{
__block NSDictionary<NSString *, id> * dataStoreSecureLocalValues = nil;
dispatch_sync(_storageDelegateQueue, ^{
if ([self->_storageDelegate respondsToSelector:@selector(valuesForController:securityLevel:sharingType:)]) {
dataStoreSecureLocalValues = [self->_storageDelegate valuesForController:self->_controller securityLevel:MTRStorageSecurityLevelSecure sharingType:MTRStorageSharingTypeNotShared];
}
});
if (dataStoreSecureLocalValues.count) {
clusterDataHandler([self _getClusterDataFromSecureLocalValues:dataStoreSecureLocalValues]);
}
}
- (nullable MTRCASESessionResumptionInfo *)findResumptionInfoByNodeID:(NSNumber *)nodeID
{
return [self _findResumptionInfoWithKey:ResumptionByNodeIDKey(nodeID)];
}
- (nullable MTRCASESessionResumptionInfo *)findResumptionInfoByResumptionID:(NSData *)resumptionID
{
return [self _findResumptionInfoWithKey:ResumptionByResumptionIDKey(resumptionID)];
}
- (void)storeResumptionInfo:(MTRCASESessionResumptionInfo *)resumptionInfo
{
auto * oldInfo = [self findResumptionInfoByNodeID:resumptionInfo.nodeID];
dispatch_sync(_storageDelegateQueue, ^{
if (oldInfo != nil) {
// Remove old resumption id key. No need to do that for the
// node id, because we are about to overwrite it.
[_storageDelegate controller:_controller
removeValueForKey:ResumptionByResumptionIDKey(oldInfo.resumptionID)
securityLevel:MTRStorageSecurityLevelSecure
sharingType:MTRStorageSharingTypeNotShared];
[_nodesWithResumptionInfo removeObject:resumptionInfo.nodeID];
}
[_storageDelegate controller:_controller
storeValue:resumptionInfo
forKey:ResumptionByNodeIDKey(resumptionInfo.nodeID)
securityLevel:MTRStorageSecurityLevelSecure
sharingType:MTRStorageSharingTypeNotShared];
[_storageDelegate controller:_controller
storeValue:resumptionInfo
forKey:ResumptionByResumptionIDKey(resumptionInfo.resumptionID)
securityLevel:MTRStorageSecurityLevelSecure
sharingType:MTRStorageSharingTypeNotShared];
// Update our resumption info node list.
[_nodesWithResumptionInfo addObject:resumptionInfo.nodeID];
[_storageDelegate controller:_controller
storeValue:[_nodesWithResumptionInfo copy]
forKey:sResumptionNodeListKey
securityLevel:MTRStorageSecurityLevelSecure
sharingType:MTRStorageSharingTypeNotShared];
});
}
- (void)clearAllResumptionInfo
{
// Can we do less dispatch? We would need to have a version of
// _findResumptionInfoWithKey that assumes we are already on the right queue.
for (NSNumber * nodeID in _nodesWithResumptionInfo) {
auto * oldInfo = [self findResumptionInfoByNodeID:nodeID];
if (oldInfo != nil) {
dispatch_sync(_storageDelegateQueue, ^{
[_storageDelegate controller:_controller
removeValueForKey:ResumptionByResumptionIDKey(oldInfo.resumptionID)
securityLevel:MTRStorageSecurityLevelSecure
sharingType:MTRStorageSharingTypeNotShared];
[_storageDelegate controller:_controller
removeValueForKey:ResumptionByNodeIDKey(oldInfo.nodeID)
securityLevel:MTRStorageSecurityLevelSecure
sharingType:MTRStorageSharingTypeNotShared];
});
}
}
[_nodesWithResumptionInfo removeAllObjects];
}
- (CHIP_ERROR)storeLastLocallyUsedNOC:(MTRCertificateTLVBytes)noc
{
__block BOOL ok;
dispatch_sync(_storageDelegateQueue, ^{
ok = [_storageDelegate controller:_controller
storeValue:noc
forKey:sLastLocallyUsedNOCKey
securityLevel:MTRStorageSecurityLevelSecure
sharingType:MTRStorageSharingTypeNotShared];
});
return ok ? CHIP_NO_ERROR : CHIP_ERROR_PERSISTED_STORAGE_FAILED;
}
- (MTRCertificateTLVBytes _Nullable)fetchLastLocallyUsedNOC
{
__block id data;
dispatch_sync(_storageDelegateQueue, ^{
@autoreleasepool {
data = [_storageDelegate controller:_controller
valueForKey:sLastLocallyUsedNOCKey
securityLevel:MTRStorageSecurityLevelSecure
sharingType:MTRStorageSharingTypeNotShared];
}
});
if (data == nil) {
return nil;
}
if (![data isKindOfClass:[NSData class]]) {
return nil;
}
return data;
}
- (nullable MTRCASESessionResumptionInfo *)_findResumptionInfoWithKey:(nullable NSString *)key
{
// key could be nil if [NSString stringWithFormat] returns nil for some reason.
if (key == nil) {
return nil;
}
__block id resumptionInfo;
dispatch_sync(_storageDelegateQueue, ^{
@autoreleasepool {
resumptionInfo = [_storageDelegate controller:_controller
valueForKey:key
securityLevel:MTRStorageSecurityLevelSecure
sharingType:MTRStorageSharingTypeNotShared];
}
});
if (resumptionInfo == nil) {
return nil;
}
if (![resumptionInfo isKindOfClass:[MTRCASESessionResumptionInfo class]]) {
return nil;
}
return resumptionInfo;
}
#pragma - Attribute Cache utility
/** MTRDevice cache storage
*
* Per controller:
* NodeID index
* key: "attrCacheNodeIndex"
* value: list of nodeIDs
* EndpointID index
* key: "attrCacheEndpointIndex:<nodeID>:endpointID"
* value: list of endpoint IDs
* ClusterID index
* key: "<nodeID+endpointID> clusters"
* value: list of cluster IDs
* AttributeID index
* key: "<nodeID+endpointID+clusterID> attributes"
* value: list of attribute IDs
* Attribute data entry:
* key: "<nodeID+endpointID+clusterID+attributeID> attribute data"
* value: serialized dictionary of attribute data
*
* Attribute data dictionary
* Additional value "serial number"
*/
- (id)_fetchAttributeCacheValueForKey:(NSString *)key expectedClass:(Class)expectedClass;
{
id data;
@autoreleasepool {
data = [_storageDelegate controller:_controller
valueForKey:key
securityLevel:MTRStorageSecurityLevelSecure
sharingType:MTRStorageSharingTypeNotShared];
}
if (data == nil) {
return nil;
}
if (![data isKindOfClass:expectedClass]) {
return nil;
}
return data;
}
- (BOOL)_storeAttributeCacheValue:(id)value forKey:(NSString *)key
{
return [_storageDelegate controller:_controller
storeValue:value
forKey:key
securityLevel:MTRStorageSecurityLevelSecure
sharingType:MTRStorageSharingTypeNotShared];
}
- (BOOL)_bulkStoreAttributeCacheValues:(NSDictionary<NSString *, id<NSSecureCoding>> *)values
{
return [_storageDelegate controller:_controller
storeValues:values
securityLevel:MTRStorageSecurityLevelSecure
sharingType:MTRStorageSharingTypeNotShared];
}
- (BOOL)_removeAttributeCacheValueForKey:(NSString *)key
{
return [_storageDelegate controller:_controller
removeValueForKey:key
securityLevel:MTRStorageSecurityLevelSecure
sharingType:MTRStorageSharingTypeNotShared];
}
static NSString * sAttributeCacheNodeIndexKey = @"attrCacheNodeIndex";
- (nullable NSArray<NSNumber *> *)_fetchNodeIndex
{
return [self _fetchAttributeCacheValueForKey:sAttributeCacheNodeIndexKey expectedClass:[NSArray class]];
}
- (BOOL)_storeNodeIndex:(NSArray<NSNumber *> *)nodeIndex
{
return [self _storeAttributeCacheValue:nodeIndex forKey:sAttributeCacheNodeIndexKey];
}
- (BOOL)_deleteNodeIndex
{
return [self _removeAttributeCacheValueForKey:sAttributeCacheNodeIndexKey];
}
static NSString * sAttributeCacheEndpointIndexKeyPrefix = @"attrCacheEndpointIndex";
- (NSString *)_endpointIndexKeyForNodeID:(NSNumber *)nodeID
{
return [sAttributeCacheEndpointIndexKeyPrefix stringByAppendingFormat:@":0x%016llX", nodeID.unsignedLongLongValue];
}
- (nullable NSArray<NSNumber *> *)_fetchEndpointIndexForNodeID:(NSNumber *)nodeID
{
if (!nodeID) {
MTR_LOG_ERROR("%s: unexpected nil input", __func__);
return nil;
}
return [self _fetchAttributeCacheValueForKey:[self _endpointIndexKeyForNodeID:nodeID] expectedClass:[NSArray class]];
}
- (BOOL)_storeEndpointIndex:(NSArray<NSNumber *> *)endpointIndex forNodeID:(NSNumber *)nodeID
{
if (!nodeID) {
MTR_LOG_ERROR("%s: unexpected nil input", __func__);
return NO;
}
return [self _storeAttributeCacheValue:endpointIndex forKey:[self _endpointIndexKeyForNodeID:nodeID]];
}
- (BOOL)_deleteEndpointIndexForNodeID:(NSNumber *)nodeID
{
if (!nodeID) {
MTR_LOG_ERROR("%s: unexpected nil input", __func__);
return NO;
}
return [self _removeAttributeCacheValueForKey:[self _endpointIndexKeyForNodeID:nodeID]];
}
static NSString * sAttributeCacheClusterIndexKeyPrefix = @"attrCacheClusterIndex";
- (NSString *)_clusterIndexKeyForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID
{
return [sAttributeCacheClusterIndexKeyPrefix stringByAppendingFormat:@":0x%016llX:%0x04X", nodeID.unsignedLongLongValue, endpointID.unsignedShortValue];
}
- (nullable NSArray<NSNumber *> *)_fetchClusterIndexForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID
{
if (!nodeID || !endpointID) {
MTR_LOG_ERROR("%s: unexpected nil input", __func__);
return nil;
}
return [self _fetchAttributeCacheValueForKey:[self _clusterIndexKeyForNodeID:nodeID endpointID:endpointID] expectedClass:[NSArray class]];
}
- (BOOL)_storeClusterIndex:(NSArray<NSNumber *> *)clusterIndex forNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID
{
if (!nodeID || !endpointID) {
MTR_LOG_ERROR("%s: unexpected nil input", __func__);
return NO;
}
return [self _storeAttributeCacheValue:clusterIndex forKey:[self _clusterIndexKeyForNodeID:nodeID endpointID:endpointID]];
}
- (BOOL)_deleteClusterIndexForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID
{
if (!nodeID || !endpointID) {
MTR_LOG_ERROR("%s: unexpected nil input", __func__);
return NO;
}
return [self _removeAttributeCacheValueForKey:[self _clusterIndexKeyForNodeID:nodeID endpointID:endpointID]];
}
static NSString * sAttributeCacheClusterDataKeyPrefix = @"attrCacheClusterData";
- (NSString *)_clusterDataKeyForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID
{
return [sAttributeCacheClusterDataKeyPrefix stringByAppendingFormat:@":0x%016llX:%0x04X:0x%08lX", nodeID.unsignedLongLongValue, endpointID.unsignedShortValue, clusterID.unsignedLongValue];
}
- (nullable MTRDeviceClusterData *)_fetchClusterDataForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID
{
if (!nodeID || !endpointID || !clusterID) {
MTR_LOG_ERROR("%s: unexpected nil input", __func__);
return nil;
}
return [self _fetchAttributeCacheValueForKey:[self _clusterDataKeyForNodeID:nodeID endpointID:endpointID clusterID:clusterID] expectedClass:[MTRDeviceClusterData class]];
}
- (BOOL)_storeClusterData:(MTRDeviceClusterData *)clusterData forNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID
{
if (!nodeID || !endpointID || !clusterID || !clusterData) {
MTR_LOG_ERROR("%s: unexpected nil input", __func__);
return NO;
}
return [self _storeAttributeCacheValue:clusterData forKey:[self _clusterDataKeyForNodeID:nodeID endpointID:endpointID clusterID:clusterID]];
}
- (BOOL)_deleteClusterDataForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID
{
if (!nodeID || !endpointID || !clusterID) {
MTR_LOG_ERROR("%s: unexpected nil input", __func__);
return NO;
}
return [self _removeAttributeCacheValueForKey:[self _clusterDataKeyForNodeID:nodeID endpointID:endpointID clusterID:clusterID]];
}
static NSString * sAttributeCacheAttributeIndexKeyPrefix = @"attrCacheAttributeIndex";
- (NSString *)_attributeIndexKeyForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID
{
return [sAttributeCacheAttributeIndexKeyPrefix stringByAppendingFormat:@":0x%016llX:0x%04X:0x%08lX", nodeID.unsignedLongLongValue, endpointID.unsignedShortValue, clusterID.unsignedLongValue];
}
- (nullable NSArray<NSNumber *> *)_fetchAttributeIndexForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID
{
return [self _fetchAttributeCacheValueForKey:[self _attributeIndexKeyForNodeID:nodeID endpointID:endpointID clusterID:clusterID] expectedClass:[NSArray class]];
}
- (BOOL)_storeAttributeIndex:(NSArray<NSNumber *> *)attributeIndex forNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID
{
return [self _storeAttributeCacheValue:attributeIndex forKey:[self _attributeIndexKeyForNodeID:nodeID endpointID:endpointID clusterID:clusterID]];
}
- (BOOL)_deleteAttributeIndexForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID
{
return [self _removeAttributeCacheValueForKey:[self _attributeIndexKeyForNodeID:nodeID endpointID:endpointID clusterID:clusterID]];
}
static NSString * sAttributeCacheAttributeValueKeyPrefix = @"attrCacheAttributeValue";
- (NSString *)_attributeValueKeyForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID attributeID:(NSNumber *)attributeID
{
return [sAttributeCacheAttributeValueKeyPrefix stringByAppendingFormat:@":0x%016llX:0x%04X:0x%08lX:0x%08lX", nodeID.unsignedLongLongValue, endpointID.unsignedShortValue, clusterID.unsignedLongValue, attributeID.unsignedLongValue];
}
- (nullable NSDictionary *)_fetchAttributeValueForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID attributeID:(NSNumber *)attributeID
{
return [self _fetchAttributeCacheValueForKey:[self _attributeValueKeyForNodeID:nodeID endpointID:endpointID clusterID:clusterID attributeID:attributeID] expectedClass:[NSDictionary class]];
}
- (BOOL)_storeAttributeValue:(NSDictionary *)value forNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID attributeID:(NSNumber *)attributeID
{
return [self _storeAttributeCacheValue:value forKey:[self _attributeValueKeyForNodeID:nodeID endpointID:endpointID clusterID:clusterID attributeID:attributeID]];
}
- (BOOL)_deleteAttributeValueForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID attributeID:(NSNumber *)attributeID
{
return [self _removeAttributeCacheValueForKey:[self _attributeValueKeyForNodeID:nodeID endpointID:endpointID clusterID:clusterID attributeID:attributeID]];
}
#pragma - Attribute Cache management
#ifndef ATTRIBUTE_CACHE_VERBOSE_LOGGING
#define ATTRIBUTE_CACHE_VERBOSE_LOGGING 0
#endif
- (nullable NSArray<NSDictionary *> *)getStoredAttributesForNodeID:(NSNumber *)nodeID
{
__block NSMutableArray * attributesToReturn = nil;
dispatch_sync(_storageDelegateQueue, ^{
// Fetch node index
NSArray<NSNumber *> * nodeIndex = [self _fetchNodeIndex];
#if ATTRIBUTE_CACHE_VERBOSE_LOGGING
MTR_LOG_INFO("Fetch got %lu values for nodeIndex", static_cast<unsigned long>(nodeIndex.count));
#endif
if (![nodeIndex containsObject:nodeID]) {
// Sanity check and delete if nodeID exists in index
NSArray<NSNumber *> * endpointIndex = [self _fetchEndpointIndexForNodeID:nodeID];
if (endpointIndex) {
MTR_LOG_ERROR("Persistent attribute cache contains orphaned entry for nodeID %@ - deleting", nodeID);
[self clearStoredAttributesForNodeID:nodeID];
}
MTR_LOG_INFO("Fetch got no value for endpointIndex @ node 0x%016llX", nodeID.unsignedLongLongValue);
attributesToReturn = nil;
return;
}
// Fetch endpoint index
NSArray<NSNumber *> * endpointIndex = [self _fetchEndpointIndexForNodeID:nodeID];
#if ATTRIBUTE_CACHE_VERBOSE_LOGGING
MTR_LOG_INFO("Fetch got %lu values for endpointIndex @ node 0x%016llX", static_cast<unsigned long>(endpointIndex.count), nodeID.unsignedLongLongValue);
#endif
for (NSNumber * endpointID in endpointIndex) {
// Fetch cluster index
NSArray<NSNumber *> * clusterIndex = [self _fetchClusterIndexForNodeID:nodeID endpointID:endpointID];
#if ATTRIBUTE_CACHE_VERBOSE_LOGGING
MTR_LOG_INFO("Fetch got %lu values for clusterIndex @ node 0x%016llX %u", static_cast<unsigned long>(clusterIndex.count), nodeID.unsignedLongLongValue, endpointID.unsignedShortValue);
#endif
for (NSNumber * clusterID in clusterIndex) {
// Fetch attribute index
NSArray<NSNumber *> * attributeIndex = [self _fetchAttributeIndexForNodeID:nodeID endpointID:endpointID clusterID:clusterID];
#if ATTRIBUTE_CACHE_VERBOSE_LOGGING
MTR_LOG_INFO("Fetch got %lu values for attributeIndex @ node 0x%016llX endpoint %u cluster 0x%08lX", static_cast<unsigned long>(attributeIndex.count), nodeID.unsignedLongLongValue, endpointID.unsignedShortValue, clusterID.unsignedLongValue);
#endif
for (NSNumber * attributeID in attributeIndex) {
NSDictionary * value = [self _fetchAttributeValueForNodeID:nodeID endpointID:endpointID clusterID:clusterID attributeID:attributeID];
#if ATTRIBUTE_CACHE_VERBOSE_LOGGING
MTR_LOG_INFO("Fetch got %u values for attribute value @ node 0x%016llX endpoint %u cluster 0x%08lX attribute 0x%08lX", value ? 1 : 0, nodeID.unsignedLongLongValue, endpointID.unsignedShortValue, clusterID.unsignedLongValue, attributeID.unsignedLongValue);
#endif
if (value) {
if (!attributesToReturn) {
attributesToReturn = [NSMutableArray array];
}
// Construct data-value dictionary and add to array
MTRAttributePath * path = [MTRAttributePath attributePathWithEndpointID:endpointID clusterID:clusterID attributeID:attributeID];
[attributesToReturn addObject:@ { MTRAttributePathKey : path, MTRDataKey : value }];
}
}
// TODO: Add per-cluster integrity check verification
}
}
});
return attributesToReturn;
}
#ifdef DEBUG
- (void)unitTestPruneEmptyStoredAttributesBranches
{
dispatch_sync(_storageDelegateQueue, ^{
[self _pruneEmptyStoredAttributesBranches];
});
}
#endif
- (void)_pruneEmptyStoredAttributesBranches
{
dispatch_assert_queue(_storageDelegateQueue);
NSUInteger storeFailures = 0;
// Fetch node index
NSArray<NSNumber *> * nodeIndex = [self _fetchNodeIndex];
NSMutableArray<NSNumber *> * nodeIndexCopy = [nodeIndex mutableCopy];
for (NSNumber * nodeID in nodeIndex) {
// Fetch endpoint index
NSArray<NSNumber *> * endpointIndex = [self _fetchEndpointIndexForNodeID:nodeID];
NSMutableArray<NSNumber *> * endpointIndexCopy = [endpointIndex mutableCopy];
for (NSNumber * endpointID in endpointIndex) {
// Fetch cluster index
NSArray<NSNumber *> * clusterIndex = [self _fetchClusterIndexForNodeID:nodeID endpointID:endpointID];
NSMutableArray<NSNumber *> * clusterIndexCopy = [clusterIndex mutableCopy];
for (NSNumber * clusterID in clusterIndex) {
// Fetch attribute index
NSArray<NSNumber *> * attributeIndex = [self _fetchAttributeIndexForNodeID:nodeID endpointID:endpointID clusterID:clusterID];
NSMutableArray<NSNumber *> * attributeIndexCopy = [attributeIndex mutableCopy];
for (NSNumber * attributeID in attributeIndex) {
NSDictionary * value = [self _fetchAttributeValueForNodeID:nodeID endpointID:endpointID clusterID:clusterID attributeID:attributeID];
if (!value) {
[attributeIndexCopy removeObject:attributeID];
}
}
if (attributeIndex.count != attributeIndexCopy.count) {
BOOL success;
BOOL clusterDataSuccess = YES;
if (attributeIndexCopy.count) {
success = [self _storeAttributeIndex:attributeIndexCopy forNodeID:nodeID endpointID:endpointID clusterID:clusterID];
} else {
[clusterIndexCopy removeObject:clusterID];
success = [self _deleteAttributeIndexForNodeID:nodeID endpointID:endpointID clusterID:clusterID];
clusterDataSuccess = [self _deleteClusterDataForNodeID:nodeID endpointID:endpointID clusterID:clusterID];
}
if (!success) {
storeFailures++;
MTR_LOG_INFO("Store failed in _pruneEmptyStoredAttributesBranches for attributeIndex (%lu) @ node 0x%016llX endpoint %u cluster 0x%08lX", static_cast<unsigned long>(attributeIndexCopy.count), nodeID.unsignedLongLongValue, endpointID.unsignedShortValue, clusterID.unsignedLongValue);
}
if (!clusterDataSuccess) {
storeFailures++;
MTR_LOG_INFO("Store failed in _pruneEmptyStoredAttributesBranches for clusterData @ node 0x%016llX endpoint %u cluster 0x%08lX", nodeID.unsignedLongLongValue, endpointID.unsignedShortValue, clusterID.unsignedLongValue);
}
}
}
if (clusterIndex.count != clusterIndexCopy.count) {
BOOL success;
if (clusterIndexCopy.count) {
success = [self _storeClusterIndex:clusterIndexCopy forNodeID:nodeID endpointID:endpointID];
} else {
[endpointIndexCopy removeObject:endpointID];
success = [self _deleteClusterIndexForNodeID:nodeID endpointID:endpointID];
}
if (!success) {
storeFailures++;
MTR_LOG_INFO("Store failed in _pruneEmptyStoredAttributesBranches for clusterIndex (%lu) @ node 0x%016llX endpoint %u", static_cast<unsigned long>(clusterIndexCopy.count), nodeID.unsignedLongLongValue, endpointID.unsignedShortValue);
}
}
}
if (endpointIndex.count != endpointIndexCopy.count) {
BOOL success;
if (endpointIndexCopy.count) {
success = [self _storeEndpointIndex:endpointIndexCopy forNodeID:nodeID];
} else {
[nodeIndexCopy removeObject:nodeID];
success = [self _deleteEndpointIndexForNodeID:nodeID];
}
if (!success) {
storeFailures++;
MTR_LOG_INFO("Store failed in _pruneEmptyStoredAttributesBranches for endpointIndex (%lu) @ node 0x%016llX", static_cast<unsigned long>(endpointIndexCopy.count), nodeID.unsignedLongLongValue);
}
}
}
if (nodeIndex.count != nodeIndexCopy.count) {
BOOL success;
if (nodeIndexCopy.count) {
success = [self _storeNodeIndex:nodeIndexCopy];
} else {
success = [self _deleteNodeIndex];
}
if (!success) {
storeFailures++;
MTR_LOG_INFO("Store failed in _pruneEmptyStoredAttributesBranches for nodeIndex (%lu)", static_cast<unsigned long>(nodeIndexCopy.count));
}
}
if (storeFailures) {
MTR_LOG_ERROR("Store failed in _pruneEmptyStoredAttributesBranches: failure count %lu", static_cast<unsigned long>(storeFailures));
}
}
- (void)storeAttributeValues:(NSArray<NSDictionary *> *)dataValues forNodeID:(NSNumber *)nodeID
{
dispatch_async(_storageDelegateQueue, ^{
NSUInteger storeFailures = 0;
for (NSDictionary * dataValue in dataValues) {
MTRAttributePath * path = dataValue[MTRAttributePathKey];
NSDictionary * value = dataValue[MTRDataKey];
#if ATTRIBUTE_CACHE_VERBOSE_LOGGING
MTR_LOG_INFO("Attempt to store attribute value @ node 0x%016llX endpoint %u cluster 0x%08lX attribute 0x%08lX", nodeID.unsignedLongLongValue, path.endpoint.unsignedShortValue, path.cluster.unsignedLongValue, path.attribute.unsignedLongValue);
#endif
BOOL storeFailed = NO;
// Ensure node index exists
NSArray<NSNumber *> * nodeIndex = [self _fetchNodeIndex];
if (!nodeIndex) {
nodeIndex = [NSArray arrayWithObject:nodeID];
storeFailed = ![self _storeNodeIndex:nodeIndex];
} else if (![nodeIndex containsObject:nodeID]) {
storeFailed = ![self _storeNodeIndex:[nodeIndex arrayByAddingObject:nodeID]];
}
if (storeFailed) {
storeFailures++;
MTR_LOG_INFO("Store failed for nodeIndex");
continue;
}
// Ensure endpoint index exists
NSArray<NSNumber *> * endpointIndex = [self _fetchEndpointIndexForNodeID:nodeID];
if (!endpointIndex) {
endpointIndex = [NSArray arrayWithObject:path.endpoint];
storeFailed = ![self _storeEndpointIndex:endpointIndex forNodeID:nodeID];
} else if (![endpointIndex containsObject:path.endpoint]) {
storeFailed = ![self _storeEndpointIndex:[endpointIndex arrayByAddingObject:path.endpoint] forNodeID:nodeID];
}
if (storeFailed) {
storeFailures++;
MTR_LOG_INFO("Store failed for endpointIndex @ node 0x%016llX", nodeID.unsignedLongLongValue);
continue;
}
// Ensure cluster index exists
NSArray<NSNumber *> * clusterIndex = [self _fetchClusterIndexForNodeID:nodeID endpointID:path.endpoint];
if (!clusterIndex) {
clusterIndex = [NSArray arrayWithObject:path.cluster];
storeFailed = ![self _storeClusterIndex:clusterIndex forNodeID:nodeID endpointID:path.endpoint];
} else if (![clusterIndex containsObject:path.cluster]) {
storeFailed = ![self _storeClusterIndex:[clusterIndex arrayByAddingObject:path.cluster] forNodeID:nodeID endpointID:path.endpoint];
}
if (storeFailed) {
storeFailures++;
MTR_LOG_INFO("Store failed for clusterIndex @ node 0x%016llX endpoint %u", nodeID.unsignedLongLongValue, path.endpoint.unsignedShortValue);
continue;
}
// TODO: Add per-cluster integrity check calculation and store with cluster
// TODO: Think about adding more integrity check for endpoint and node levels as well
// Ensure attribute index exists
NSArray<NSNumber *> * attributeIndex = [self _fetchAttributeIndexForNodeID:nodeID endpointID:path.endpoint clusterID:path.cluster];
if (!attributeIndex) {
attributeIndex = [NSArray arrayWithObject:path.attribute];
storeFailed = ![self _storeAttributeIndex:attributeIndex forNodeID:nodeID endpointID:path.endpoint clusterID:path.cluster];
} else if (![attributeIndex containsObject:path.attribute]) {
storeFailed = ![self _storeAttributeIndex:[attributeIndex arrayByAddingObject:path.attribute] forNodeID:nodeID endpointID:path.endpoint clusterID:path.cluster];
}
if (storeFailed) {
storeFailures++;
MTR_LOG_INFO("Store failed for attributeIndex @ node 0x%016llX endpoint %u cluster 0x%08lX", nodeID.unsignedLongLongValue, path.endpoint.unsignedShortValue, path.cluster.unsignedLongValue);
continue;
}
// Store value
storeFailed = ![self _storeAttributeValue:value forNodeID:nodeID endpointID:path.endpoint clusterID:path.cluster attributeID:path.attribute];
if (storeFailed) {
storeFailures++;
MTR_LOG_INFO("Store failed for attribute value @ node 0x%016llX endpoint %u cluster 0x%08lX attribute 0x%08lX", nodeID.unsignedLongLongValue, path.endpoint.unsignedShortValue, path.cluster.unsignedLongValue, path.attribute.unsignedLongValue);
}
}
// In the rare event that store fails, allow all attribute store attempts to go through and prune empty branches at the end altogether.
if (storeFailures) {
[self _pruneEmptyStoredAttributesBranches];
MTR_LOG_ERROR("Store failed in -storeAttributeValues:forNodeID: failure count %lu", static_cast<unsigned long>(storeFailures));
}
});
}
- (void)_clearStoredAttributesForNodeID:(NSNumber *)nodeID
{
NSUInteger endpointsClearAttempts = 0;
NSUInteger clustersClearAttempts = 0;
NSUInteger clusterDataClearAttempts = 0;
NSUInteger attributesClearAttempts = 0;
NSUInteger endpointsCleared = 0;
NSUInteger clustersCleared = 0;
NSUInteger clusterDataCleared = 0;
NSUInteger attributesCleared = 0;
// Fetch endpoint index
NSArray<NSNumber *> * endpointIndex = [self _fetchEndpointIndexForNodeID:nodeID];
endpointsClearAttempts += endpointIndex.count;
for (NSNumber * endpointID in endpointIndex) {
// Fetch cluster index
NSArray<NSNumber *> * clusterIndex = [self _fetchClusterIndexForNodeID:nodeID endpointID:endpointID];
clustersClearAttempts += clusterIndex.count;
clusterDataClearAttempts += clusterIndex.count; // Assuming every cluster has cluster data
for (NSNumber * clusterID in clusterIndex) {
// Fetch attribute index
NSArray<NSNumber *> * attributeIndex = [self _fetchAttributeIndexForNodeID:nodeID endpointID:endpointID clusterID:clusterID];
attributesClearAttempts += attributeIndex.count;
for (NSNumber * attributeID in attributeIndex) {
BOOL success = [self _deleteAttributeValueForNodeID:nodeID endpointID:endpointID clusterID:clusterID attributeID:attributeID];
if (!success) {
MTR_LOG_INFO("Delete failed for attribute value @ node 0x%016llX endpoint %u cluster 0x%08lX attribute 0x%08lX", nodeID.unsignedLongLongValue, endpointID.unsignedShortValue, clusterID.unsignedLongValue, attributeID.unsignedLongValue);
} else {
attributesCleared++;
}
}
BOOL success = [self _deleteAttributeIndexForNodeID:nodeID endpointID:endpointID clusterID:clusterID];
if (!success) {
MTR_LOG_INFO("Delete failed for attributeIndex @ node 0x%016llX endpoint %u cluster 0x%08lX", nodeID.unsignedLongLongValue, endpointID.unsignedShortValue, clusterID.unsignedLongValue);
} else {
clustersCleared++;
}
success = [self _deleteClusterDataForNodeID:nodeID endpointID:endpointID clusterID:clusterID];
if (!success) {
MTR_LOG_INFO("Delete failed for clusterData @ node 0x%016llX endpoint %u cluster 0x%08lX", nodeID.unsignedLongLongValue, endpointID.unsignedShortValue, clusterID.unsignedLongValue);
} else {
clusterDataCleared++;
}
}
BOOL success = [self _deleteClusterIndexForNodeID:nodeID endpointID:endpointID];
if (!success) {
MTR_LOG_INFO("Delete failed for clusterIndex @ node 0x%016llX endpoint %u", nodeID.unsignedLongLongValue, endpointID.unsignedShortValue);
} else {
endpointsCleared++;
}
}
BOOL success = [self _deleteEndpointIndexForNodeID:nodeID];
if (!success) {
MTR_LOG_INFO("Delete failed for endpointrIndex @ node 0x%016llX", nodeID.unsignedLongLongValue);
}
MTR_LOG_INFO("clearStoredAttributesForNodeID: deleted endpoints %lu/%lu clusters %lu/%lu clusterData %lu/%lu attributes %lu/%lu", static_cast<unsigned long>(endpointsCleared), static_cast<unsigned long>(endpointsClearAttempts), static_cast<unsigned long>(clustersCleared), static_cast<unsigned long>(clustersClearAttempts), static_cast<unsigned long>(clusterDataCleared), static_cast<unsigned long>(clusterDataClearAttempts), static_cast<unsigned long>(attributesCleared), static_cast<unsigned long>(attributesClearAttempts));
}
- (void)clearStoredAttributesForNodeID:(NSNumber *)nodeID
{
dispatch_async(_storageDelegateQueue, ^{
[self _clearStoredAttributesForNodeID:nodeID];
NSArray<NSNumber *> * nodeIndex = [self _fetchNodeIndex];
NSMutableArray<NSNumber *> * nodeIndexCopy = [nodeIndex mutableCopy];
[nodeIndexCopy removeObject:nodeID];
if (nodeIndex.count != nodeIndexCopy.count) {
BOOL success;
if (nodeIndexCopy.count) {
success = [self _storeNodeIndex:nodeIndexCopy];
} else {
success = [self _deleteNodeIndex];
}
if (!success) {
MTR_LOG_INFO("Store failed in clearStoredAttributesForNodeID for nodeIndex (%lu)", static_cast<unsigned long>(nodeIndexCopy.count));
}
}
});
}
- (void)clearAllStoredAttributes
{
dispatch_async(_storageDelegateQueue, ^{
// Fetch node index
NSArray<NSNumber *> * nodeIndex = [self _fetchNodeIndex];
for (NSNumber * nodeID in nodeIndex) {
[self _clearStoredAttributesForNodeID:nodeID];
}
BOOL success = [self _deleteNodeIndex];
if (!success) {
MTR_LOG_INFO("Delete failed for nodeIndex");
}
});
}
- (nullable NSDictionary<MTRClusterPath *, MTRDeviceClusterData *> *)getStoredClusterDataForNodeID:(NSNumber *)nodeID
{
if (!nodeID) {
MTR_LOG_ERROR("%s: unexpected nil input", __func__);
return nil;
}
__block NSMutableDictionary<MTRClusterPath *, MTRDeviceClusterData *> * clusterDataToReturn = nil;
dispatch_sync(_storageDelegateQueue, ^{
// Fetch node index
NSArray<NSNumber *> * nodeIndex = [self _fetchNodeIndex];
#if ATTRIBUTE_CACHE_VERBOSE_LOGGING
MTR_LOG_INFO("Fetch got %lu values for nodeIndex", static_cast<unsigned long>(nodeIndex.count));
#endif
if (![nodeIndex containsObject:nodeID]) {
// Sanity check and delete if nodeID exists in index
NSArray<NSNumber *> * endpointIndex = [self _fetchEndpointIndexForNodeID:nodeID];
if (endpointIndex) {
MTR_LOG_ERROR("Persistent attribute cache contains orphaned entry for nodeID %@ - deleting", nodeID);
[self clearStoredAttributesForNodeID:nodeID];
}
MTR_LOG_INFO("Fetch got no value for endpointIndex @ node 0x%016llX", nodeID.unsignedLongLongValue);
clusterDataToReturn = nil;
return;
}
// Fetch endpoint index
NSArray<NSNumber *> * endpointIndex = [self _fetchEndpointIndexForNodeID:nodeID];
#if ATTRIBUTE_CACHE_VERBOSE_LOGGING
MTR_LOG_INFO("Fetch got %lu values for endpointIndex @ node 0x%016llX", static_cast<unsigned long>(endpointIndex.count), nodeID.unsignedLongLongValue);
#endif
for (NSNumber * endpointID in endpointIndex) {
// Fetch cluster index
NSArray<NSNumber *> * clusterIndex = [self _fetchClusterIndexForNodeID:nodeID endpointID:endpointID];
#if ATTRIBUTE_CACHE_VERBOSE_LOGGING
MTR_LOG_INFO("Fetch got %lu values for clusterIndex @ node 0x%016llX %u", static_cast<unsigned long>(clusterIndex.count), nodeID.unsignedLongLongValue, endpointID.unsignedShortValue);
#endif
for (NSNumber * clusterID in clusterIndex) {
// Fetch cluster data
MTRDeviceClusterData * clusterData = [self _fetchClusterDataForNodeID:nodeID endpointID:endpointID clusterID:clusterID];
if (!clusterData) {
MTR_LOG_INFO("Fetch got no value for clusterData @ node 0x%016llX endpoint %u cluster 0x%08lX", nodeID.unsignedLongLongValue, endpointID.unsignedShortValue, clusterID.unsignedLongValue);
continue;
}
#if ATTRIBUTE_CACHE_VERBOSE_LOGGING
MTR_LOG_INFO("Fetch got clusterData @ node 0x%016llX endpoint %u cluster 0x%08lX", nodeID.unsignedLongLongValue, endpointID.unsignedShortValue, clusterID.unsignedLongValue);
#endif
MTRClusterPath * path = [MTRClusterPath clusterPathWithEndpointID:endpointID clusterID:clusterID];
if (!clusterDataToReturn) {
clusterDataToReturn = [NSMutableDictionary dictionary];
}
clusterDataToReturn[path] = clusterData;
}
}
});
return clusterDataToReturn;
}
// Utility for constructing dictionary of nodeID to cluster data from dictionary of storage keys
- (nullable NSDictionary<NSNumber *, NSDictionary<MTRClusterPath *, MTRDeviceClusterData *> *> *)_getClusterDataFromSecureLocalValues:(NSDictionary<NSString *, id> *)secureLocalValues
{
NSMutableDictionary<NSNumber *, NSDictionary<MTRClusterPath *, MTRDeviceClusterData *> *> * clusterDataByNodeToReturn = nil;
if (![secureLocalValues isKindOfClass:[NSDictionary class]]) {
return nil;
}
// Fetch node index
NSArray<NSNumber *> * nodeIndex = secureLocalValues[sAttributeCacheNodeIndexKey];
if (![nodeIndex isKindOfClass:[NSArray class]]) {
return nil;
}
for (NSNumber * nodeID in nodeIndex) {
if (![nodeID isKindOfClass:[NSNumber class]]) {
continue;
}
NSMutableDictionary<MTRClusterPath *, MTRDeviceClusterData *> * clusterDataForNode = nil;
NSArray<NSNumber *> * endpointIndex = secureLocalValues[[self _endpointIndexKeyForNodeID:nodeID]];
if (![endpointIndex isKindOfClass:[NSArray class]]) {
continue;
}
for (NSNumber * endpointID in endpointIndex) {
if (![endpointID isKindOfClass:[NSNumber class]]) {
continue;
}
NSArray<NSNumber *> * clusterIndex = secureLocalValues[[self _clusterIndexKeyForNodeID:nodeID endpointID:endpointID]];
if (![clusterIndex isKindOfClass:[NSArray class]]) {
continue;
}
for (NSNumber * clusterID in clusterIndex) {
if (![clusterID isKindOfClass:[NSNumber class]]) {
continue;
}
MTRDeviceClusterData * clusterData = secureLocalValues[[self _clusterDataKeyForNodeID:nodeID endpointID:endpointID clusterID:clusterID]];
if (!clusterData) {
continue;
}
if (![clusterData isKindOfClass:[MTRDeviceClusterData class]]) {
continue;
}
MTRClusterPath * clusterPath = [MTRClusterPath clusterPathWithEndpointID:endpointID clusterID:clusterID];
if (!clusterDataForNode) {
clusterDataForNode = [NSMutableDictionary dictionary];
}
clusterDataForNode[clusterPath] = clusterData;
}
}
if (clusterDataForNode.count) {
if (!clusterDataByNodeToReturn) {
clusterDataByNodeToReturn = [NSMutableDictionary dictionary];
}
clusterDataByNodeToReturn[nodeID] = clusterDataForNode;
}
}
return clusterDataByNodeToReturn;
}
- (void)storeClusterData:(NSDictionary<MTRClusterPath *, MTRDeviceClusterData *> *)clusterData forNodeID:(NSNumber *)nodeID
{
if (!nodeID) {
MTR_LOG_ERROR("%s: unexpected nil input", __func__);
return;
}
if (!clusterData.count) {
MTR_LOG_ERROR("%s: nothing to store", __func__);
return;
}
dispatch_async(_storageDelegateQueue, ^{
NSUInteger storeFailures = 0;
NSMutableDictionary<NSString *, id<NSSecureCoding>> * bulkValuesToStore = nil;
if ([self->_storageDelegate respondsToSelector:@selector(controller:storeValues:securityLevel:sharingType:)]) {
bulkValuesToStore = [NSMutableDictionary dictionary];
}
// A map of endpoint => list of clusters modified for that endpoint so cluster indexes can be updated later
NSMutableDictionary<NSNumber *, NSMutableSet<NSNumber *> *> * clustersModified = [NSMutableDictionary dictionary];
// Write cluster specific data first
for (MTRClusterPath * path in clusterData) {
MTRDeviceClusterData * data = clusterData[path];
#if ATTRIBUTE_CACHE_VERBOSE_LOGGING
MTR_LOG_INFO("Attempt to store clusterData @ node 0x%016llX endpoint %u cluster 0x%08lX", nodeID.unsignedLongLongValue, path.endpoint.unsignedShortValue, path.cluster.unsignedLongValue);
#endif
if (bulkValuesToStore) {
bulkValuesToStore[[self _clusterDataKeyForNodeID:nodeID endpointID:path.endpoint clusterID:path.cluster]] = data;
} else {
// Store cluster data
BOOL storeFailed = ![self _storeClusterData:data forNodeID:nodeID endpointID:path.endpoint clusterID:path.cluster];
if (storeFailed) {
storeFailures++;
MTR_LOG_INFO("Store failed for clusterData @ node 0x%016llX endpoint %u cluster 0x%08lX", nodeID.unsignedLongLongValue, path.endpoint.unsignedShortValue, path.cluster.unsignedLongValue);
}
}
// Note the cluster as modified for the endpoint
NSMutableSet<NSNumber *> * clustersInEndpoint = clustersModified[path.endpoint];
if (!clustersInEndpoint) {
clustersInEndpoint = [NSMutableSet set];
clustersModified[path.endpoint] = clustersInEndpoint;
}
[clustersInEndpoint addObject:path.cluster];
}
// Prepare to tally all endpoints touched
NSArray<NSNumber *> * endpointIndex = [self _fetchEndpointIndexForNodeID:nodeID];
BOOL endpointIndexModified = NO;
NSMutableArray<NSNumber *> * endpointIndexToStore;
if (endpointIndex) {
endpointIndexToStore = [endpointIndex mutableCopy];
} else {
endpointIndexToStore = [NSMutableArray array];
endpointIndexModified = YES;
}
for (NSNumber * endpointID in clustersModified) {
if (![endpointIndexToStore containsObject:endpointID]) {
[endpointIndexToStore addObject:endpointID];
endpointIndexModified = YES;
}
// Get cluster index from storage and prepare to tally clusters touched
NSSet * newClusters = clustersModified[endpointID];
NSArray<NSNumber *> * clusterIndex = [self _fetchClusterIndexForNodeID:nodeID endpointID:endpointID];
BOOL clusterIndexModified = NO;
NSMutableArray<NSNumber *> * clusterIndexToStore;
if (clusterIndex) {
clusterIndexToStore = [clusterIndex mutableCopy];
} else {
clusterIndexToStore = [NSMutableArray array];
clusterIndexModified = YES;
}
for (NSNumber * clusterID in newClusters) {
if (![clusterIndexToStore containsObject:clusterID]) {
[clusterIndexToStore addObject:clusterID];
clusterIndexModified = YES;
}
}
if (clusterIndexModified) {
if (bulkValuesToStore) {
bulkValuesToStore[[self _clusterIndexKeyForNodeID:nodeID endpointID:endpointID]] = clusterIndexToStore;
} else {
BOOL storeFailed = ![self _storeClusterIndex:clusterIndexToStore forNodeID:nodeID endpointID:endpointID];
if (storeFailed) {
storeFailures++;
MTR_LOG_INFO("Store failed for clusterIndex @ node 0x%016llX endpoint %u", nodeID.unsignedLongLongValue, endpointID.unsignedShortValue);
continue;
}
}
}
}
// Update endpoint index as needed
if (endpointIndexModified) {
if (bulkValuesToStore) {
bulkValuesToStore[[self _endpointIndexKeyForNodeID:nodeID]] = endpointIndexToStore;
} else {
BOOL storeFailed = ![self _storeEndpointIndex:endpointIndexToStore forNodeID:nodeID];
if (storeFailed) {
storeFailures++;
MTR_LOG_INFO("Store failed for endpointIndex @ node 0x%016llX", nodeID.unsignedLongLongValue);
}
}
}
// Check if node index needs updating / creation
NSArray<NSNumber *> * nodeIndex = [self _fetchNodeIndex];
NSArray<NSNumber *> * nodeIndexToStore = nil;
if (!nodeIndex) {
// Ensure node index exists
nodeIndexToStore = [NSArray arrayWithObject:nodeID];
} else if (![nodeIndex containsObject:nodeID]) {
nodeIndexToStore = [nodeIndex arrayByAddingObject:nodeID];
}
if (nodeIndexToStore) {
if (bulkValuesToStore) {
bulkValuesToStore[sAttributeCacheNodeIndexKey] = nodeIndexToStore;
} else {
BOOL storeFailed = ![self _storeNodeIndex:nodeIndexToStore];
if (storeFailed) {
storeFailures++;
MTR_LOG_INFO("Store failed for nodeIndex");
}
}
}
if (bulkValuesToStore) {
BOOL storeFailed = ![self _bulkStoreAttributeCacheValues:bulkValuesToStore];
if (storeFailed) {
storeFailures++;
MTR_LOG_INFO("Store failed for bulk values count %lu", static_cast<unsigned long>(bulkValuesToStore.count));
}
}
// In the rare event that store fails, allow all attribute store attempts to go through and prune empty branches at the end altogether.
if (storeFailures) {
[self _pruneEmptyStoredAttributesBranches];
MTR_LOG_ERROR("Store failed in -storeAttributeValues:forNodeID: failure count %lu", static_cast<unsigned long>(storeFailures));
}
});
}
@end
@implementation MTRCASESessionResumptionInfo
#pragma mark - NSSecureCoding
static NSString * const sNodeIDKey = @"nodeID";
static NSString * const sResumptionIDKey = @"resumptionID";
static NSString * const sSharedSecretKey = @"sharedSecret";
static NSString * const sCATsKey = @"CATs";
+ (BOOL)supportsSecureCoding
{
return YES;
}
- (nullable instancetype)initWithCoder:(NSCoder *)decoder
{
self = [super init];
if (self == nil) {
return nil;
}
_nodeID = [decoder decodeObjectOfClass:[NSNumber class] forKey:sNodeIDKey];
// For some built-in classes decoder will decode to them even if we ask for a
// different class (!). So sanity-check what we got.
if (_nodeID != nil && ![_nodeID isKindOfClass:[NSNumber class]]) {
MTR_LOG_ERROR("MTRCASESessionResumptionInfo got %@ for node ID, not NSNumber.", _nodeID);
return nil;
}
if (!IsValidNodeIDNumber(_nodeID)) {
MTR_LOG_ERROR("MTRCASESessionResumptionInfo node ID has invalid value: %@", _nodeID);
return nil;
}
_resumptionID = [decoder decodeObjectOfClass:[NSData class] forKey:sResumptionIDKey];
if (_resumptionID != nil && ![_resumptionID isKindOfClass:[NSData class]]) {
MTR_LOG_ERROR("MTRCASESessionResumptionInfo got %@ for resumption ID, not NSData.", _resumptionID);
return nil;
}
_sharedSecret = [decoder decodeObjectOfClass:[NSData class] forKey:sSharedSecretKey];
if (_sharedSecret != nil && ![_sharedSecret isKindOfClass:[NSData class]]) {
MTR_LOG_ERROR("MTRCASESessionResumptionInfo got %@ for shared secret, not NSData.", _sharedSecret);
return nil;
}
auto caseAuthenticatedTagArray = [decoder decodeArrayOfObjectsOfClass:[NSNumber class] forKey:sCATsKey];
for (id value in caseAuthenticatedTagArray) {
if (!IsValidCATNumber(value)) {
MTR_LOG_ERROR("MTRCASESessionResumptionInfo CASE tag has invalid value: %@", value);
return nil;
}
// Range-checking will be done when we try to convert the set to CATValues.
}
_caseAuthenticatedTags = [NSSet setWithArray:caseAuthenticatedTagArray];
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder
{
[coder encodeObject:self.nodeID forKey:sNodeIDKey];
[coder encodeObject:self.resumptionID forKey:sResumptionIDKey];
[coder encodeObject:self.sharedSecret forKey:sSharedSecretKey];
// Encode the CATs as an array, so that we can decodeArrayOfObjectsOfClass
// to get type-safe decoding for them.
[coder encodeObject:[self.caseAuthenticatedTags allObjects] forKey:sCATsKey];
}
@end
NSSet<Class> * MTRDeviceControllerStorageClasses()
{
// This only needs to return the classes for toplevel things we are storing,
// plus NSNumber because some archivers use that internally to store
// information about what's being archived.
static NSSet * const sStorageClasses = [NSSet setWithArray:@[
[NSNumber class],
[NSData class],
[NSArray class],
[MTRCASESessionResumptionInfo class],
[NSDictionary class],
[NSString class],
[MTRAttributePath class],
[MTRDeviceClusterData class],
]];
return sStorageClasses;
}