Skip to content

Commit 94936ea

Browse files
Add MTRDeviceController API to get a list of node IDs with data stored for them. (#37344)
* Add MTRDeviceController API to get a list of node IDs with data stored for them. To avoid races between modifications to _nodesWithResumptionInfo and our reading it, added a lock to protect _nodesWithResumptionInfo. Also added a cached _nodesWithAttributeInfo (protected by the same lock) so that we don't have to do a sync dispatch to our storage queue to return a value from this API. * Fix unit test that was relying on the storage being the source of truth for the node index.
1 parent 95ba8a4 commit 94936ea

9 files changed

+138
-32
lines changed

src/darwin/Framework/CHIP/MTRDeviceController.h

+7
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,13 @@ MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1))
309309
*/
310310
- (void)resume MTR_AVAILABLE(ios(18.2), macos(15.2), watchos(11.2), tvos(18.2));
311311

312+
/**
313+
* Returns the list of node IDs for which this controller has stored
314+
* information. Returns empty list if the controller does not have any
315+
* information stored.
316+
*/
317+
- (NSArray<NSNumber *> *)nodesWithStoredData MTR_AVAILABLE(ios(18.4), macos(15.4), watchos(11.4), tvos(18.4));
318+
312319
/**
313320
* Shut down the controller. Calls to shutdown after the first one are NO-OPs.
314321
* This must be called, either directly or via shutting down the

src/darwin/Framework/CHIP/MTRDeviceController.mm

+6
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,12 @@ + (void)forceLocalhostAdvertisingOnly
508508
}
509509
#endif // DEBUG
510510

511+
- (NSArray<NSNumber *> *)nodesWithStoredData
512+
{
513+
MTR_ABSTRACT_METHOD();
514+
return @[];
515+
}
516+
511517
#pragma mark - MTRDeviceControllerDelegate management
512518

513519
// Note these are implemented in the base class so that XPC subclass can use it as well

src/darwin/Framework/CHIP/MTRDeviceControllerDataStore.h

+6
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,12 @@ typedef void (^MTRDeviceControllerDataStoreClusterDataHandler)(NSDictionary<NSNu
105105
*/
106106
- (void)synchronouslyPerformBlock:(void (^_Nullable)(void))block;
107107

108+
/**
109+
* Returns the list of node IDs for which this data store has stored data of
110+
* some sort.
111+
*/
112+
- (NSArray<NSNumber *> *)nodesWithStoredData;
113+
108114
@end
109115

110116
NS_ASSUME_NONNULL_END

src/darwin/Framework/CHIP/MTRDeviceControllerDataStore.mm

+92-32
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
// Importing MTRBaseDevice.h for the MTRAttributePath class. Needs to change when https://github.com/project-chip/connectedhomeip/issues/31247 is fixed.
2020
#import "MTRBaseDevice.h"
2121
#import "MTRLogging_Internal.h"
22+
#import "MTRUnfairLock.h"
2223

2324
#include <lib/core/CASEAuthTag.h>
2425
#include <lib/core/NodeId.h>
@@ -111,6 +112,11 @@ @implementation MTRDeviceControllerDataStore {
111112
__weak MTRDeviceController * _controller;
112113
// Array of nodes with resumption info, oldest-stored first.
113114
NSMutableArray<NSNumber *> * _nodesWithResumptionInfo;
115+
// Array of nodes with attribute info.
116+
NSMutableArray<NSNumber *> * _nodesWithAttributeInfo;
117+
// Lock protecting access to the _nodesWithAttributeInfo and
118+
// _nodesWithResumptionInfo arrays.
119+
os_unfair_lock _nodeArrayLock;
114120
}
115121

116122
- (nullable instancetype)initWithController:(MTRDeviceController *)controller
@@ -124,8 +130,10 @@ - (nullable instancetype)initWithController:(MTRDeviceController *)controller
124130
_controller = controller;
125131
_storageDelegate = storageDelegate;
126132
_storageDelegateQueue = storageDelegateQueue;
133+
_nodeArrayLock = OS_UNFAIR_LOCK_INIT;
127134

128135
__block id resumptionNodeList;
136+
__block NSArray<NSNumber *> * nodesWithAttributeInfo;
129137
dispatch_sync(_storageDelegateQueue, ^{
130138
@autoreleasepool {
131139
// NOTE: controller, not our weak ref, since we know it's still
@@ -134,6 +142,8 @@ - (nullable instancetype)initWithController:(MTRDeviceController *)controller
134142
valueForKey:sResumptionNodeListKey
135143
securityLevel:MTRStorageSecurityLevelSecure
136144
sharingType:MTRStorageSharingTypeNotShared];
145+
146+
nodesWithAttributeInfo = [self _fetchNodeIndex];
137147
}
138148
});
139149
if (resumptionNodeList != nil) {
@@ -152,6 +162,12 @@ - (nullable instancetype)initWithController:(MTRDeviceController *)controller
152162
_nodesWithResumptionInfo = [[NSMutableArray alloc] init];
153163
}
154164

165+
if (nodesWithAttributeInfo != nil) {
166+
_nodesWithAttributeInfo = [nodesWithAttributeInfo mutableCopy];
167+
} else {
168+
_nodesWithAttributeInfo = [[NSMutableArray alloc] init];
169+
}
170+
155171
return self;
156172
}
157173

@@ -196,6 +212,8 @@ - (void)storeResumptionInfo:(MTRCASESessionResumptionInfo *)resumptionInfo
196212
removeValueForKey:ResumptionByResumptionIDKey(oldInfo.resumptionID)
197213
securityLevel:MTRStorageSecurityLevelSecure
198214
sharingType:MTRStorageSharingTypeNotShared];
215+
216+
std::lock_guard lock(self->_nodeArrayLock);
199217
[_nodesWithResumptionInfo removeObject:resumptionInfo.nodeID];
200218
}
201219

@@ -211,9 +229,14 @@ - (void)storeResumptionInfo:(MTRCASESessionResumptionInfo *)resumptionInfo
211229
sharingType:MTRStorageSharingTypeNotShared];
212230

213231
// Update our resumption info node list.
214-
[_nodesWithResumptionInfo addObject:resumptionInfo.nodeID];
232+
NSArray<NSNumber *> * valueToStore;
233+
{
234+
std::lock_guard lock(self->_nodeArrayLock);
235+
[_nodesWithResumptionInfo addObject:resumptionInfo.nodeID];
236+
valueToStore = [_nodesWithResumptionInfo copy];
237+
}
215238
[_storageDelegate controller:controller
216-
storeValue:[_nodesWithResumptionInfo copy]
239+
storeValue:valueToStore
217240
forKey:sResumptionNodeListKey
218241
securityLevel:MTRStorageSecurityLevelSecure
219242
sharingType:MTRStorageSharingTypeNotShared];
@@ -226,7 +249,9 @@ - (void)clearAllResumptionInfo
226249
VerifyOrReturn(controller != nil); // No way to call delegate without controller.
227250

228251
// Can we do less dispatch? We would need to have a version of
229-
// _findResumptionInfoWithKey that assumes we are already on the right queue.
252+
// _findResumptionInfoWithKey that assumes we are already on the right
253+
// queue.
254+
std::lock_guard lock(_nodeArrayLock);
230255
for (NSNumber * nodeID in _nodesWithResumptionInfo) {
231256
[self _clearResumptionInfoForNodeID:nodeID controller:controller];
232257
}
@@ -240,6 +265,8 @@ - (void)clearResumptionInfoForNodeID:(NSNumber *)nodeID
240265
VerifyOrReturn(controller != nil); // No way to call delegate without controller.
241266

242267
[self _clearResumptionInfoForNodeID:nodeID controller:controller];
268+
269+
std::lock_guard lock(_nodeArrayLock);
243270
[_nodesWithResumptionInfo removeObject:nodeID];
244271
}
245272

@@ -588,6 +615,20 @@ - (void)unitTestPruneEmptyStoredClusterDataBranches
588615
[self _pruneEmptyStoredClusterDataBranches];
589616
});
590617
}
618+
619+
- (void)unitTestRereadNodeIndex
620+
{
621+
dispatch_sync(_storageDelegateQueue, ^{
622+
auto * newIndex = [self _fetchNodeIndex];
623+
624+
std::lock_guard lock(self->_nodeArrayLock);
625+
if (newIndex != nil) {
626+
self->_nodesWithAttributeInfo = [newIndex mutableCopy];
627+
} else {
628+
self->_nodesWithAttributeInfo = [[NSMutableArray alloc] init];
629+
}
630+
});
631+
}
591632
#endif
592633

593634
- (void)_pruneEmptyStoredClusterDataBranches
@@ -596,9 +637,8 @@ - (void)_pruneEmptyStoredClusterDataBranches
596637

597638
NSUInteger storeFailures = 0;
598639

599-
// Fetch node index
600-
NSArray<NSNumber *> * nodeIndex = [self _fetchNodeIndex];
601-
NSMutableArray<NSNumber *> * nodeIndexCopy = [nodeIndex mutableCopy];
640+
std::lock_guard lock(self->_nodeArrayLock);
641+
NSArray<NSNumber *> * nodeIndex = [_nodesWithAttributeInfo copy];
602642

603643
for (NSNumber * nodeID in nodeIndex) {
604644
// Fetch endpoint index
@@ -638,7 +678,7 @@ - (void)_pruneEmptyStoredClusterDataBranches
638678
if (endpointIndexCopy.count) {
639679
success = [self _storeEndpointIndex:endpointIndexCopy forNodeID:nodeID];
640680
} else {
641-
[nodeIndexCopy removeObject:nodeID];
681+
[_nodesWithAttributeInfo removeObject:nodeID];
642682
success = [self _deleteEndpointIndexForNodeID:nodeID];
643683
}
644684
if (!success) {
@@ -648,16 +688,16 @@ - (void)_pruneEmptyStoredClusterDataBranches
648688
}
649689
}
650690

651-
if (nodeIndex.count != nodeIndexCopy.count) {
691+
if (nodeIndex.count != _nodesWithAttributeInfo.count) {
652692
BOOL success;
653-
if (nodeIndexCopy.count) {
654-
success = [self _storeNodeIndex:nodeIndexCopy];
693+
if (_nodesWithAttributeInfo.count) {
694+
success = [self _storeNodeIndex:_nodesWithAttributeInfo];
655695
} else {
656696
success = [self _deleteNodeIndex];
657697
}
658698
if (!success) {
659699
storeFailures++;
660-
MTR_LOG_ERROR("Store failed in _pruneEmptyStoredClusterDataBranches for nodeIndex (%lu)", static_cast<unsigned long>(nodeIndexCopy.count));
700+
MTR_LOG_ERROR("Store failed in _pruneEmptyStoredClusterDataBranches for nodeIndex (%lu)", static_cast<unsigned long>(_nodesWithAttributeInfo.count));
661701
}
662702
}
663703

@@ -713,18 +753,19 @@ - (void)clearStoredClusterDataForNodeID:(NSNumber *)nodeID
713753
{
714754
dispatch_async(_storageDelegateQueue, ^{
715755
[self _clearStoredClusterDataForNodeID:nodeID];
716-
NSArray<NSNumber *> * nodeIndex = [self _fetchNodeIndex];
717-
NSMutableArray<NSNumber *> * nodeIndexCopy = [nodeIndex mutableCopy];
718-
[nodeIndexCopy removeObject:nodeID];
719-
if (nodeIndex.count != nodeIndexCopy.count) {
756+
757+
std::lock_guard lock(self->_nodeArrayLock);
758+
auto oldCount = self->_nodesWithAttributeInfo.count;
759+
[self->_nodesWithAttributeInfo removeObject:nodeID];
760+
if (self->_nodesWithAttributeInfo.count != oldCount) {
720761
BOOL success;
721-
if (nodeIndexCopy.count) {
722-
success = [self _storeNodeIndex:nodeIndexCopy];
762+
if (self->_nodesWithAttributeInfo.count) {
763+
success = [self _storeNodeIndex:self->_nodesWithAttributeInfo];
723764
} else {
724765
success = [self _deleteNodeIndex];
725766
}
726767
if (!success) {
727-
MTR_LOG_ERROR("Store failed in clearStoredAttributesForNodeID for nodeIndex (%lu)", static_cast<unsigned long>(nodeIndexCopy.count));
768+
MTR_LOG_ERROR("Store failed in clearStoredAttributesForNodeID for nodeIndex (%lu)", static_cast<unsigned long>(self->_nodesWithAttributeInfo.count));
728769
}
729770
}
730771
});
@@ -804,12 +845,12 @@ - (void)clearAllStoredClusterData
804845
{
805846
dispatch_async(_storageDelegateQueue, ^{
806847
// Fetch node index
807-
NSArray<NSNumber *> * nodeIndex = [self _fetchNodeIndex];
808-
809-
for (NSNumber * nodeID in nodeIndex) {
848+
std::lock_guard lock(self->_nodeArrayLock);
849+
for (NSNumber * nodeID in self->_nodesWithAttributeInfo) {
810850
[self _clearStoredClusterDataForNodeID:nodeID];
811851
}
812852

853+
[self->_nodesWithAttributeInfo removeAllObjects];
813854
BOOL success = [self _deleteNodeIndex];
814855
if (!success) {
815856
MTR_LOG_ERROR("Delete failed for nodeIndex");
@@ -826,14 +867,13 @@ - (void)clearAllStoredClusterData
826867

827868
__block NSMutableDictionary<MTRClusterPath *, MTRDeviceClusterData *> * clusterDataToReturn = nil;
828869
dispatch_sync(_storageDelegateQueue, ^{
829-
// Fetch node index
830-
NSArray<NSNumber *> * nodeIndex = [self _fetchNodeIndex];
870+
std::lock_guard lock(self->_nodeArrayLock);
831871

832872
#if ATTRIBUTE_CACHE_VERBOSE_LOGGING
833-
MTR_LOG("Fetch got %lu values for nodeIndex", static_cast<unsigned long>(nodeIndex.count));
873+
MTR_LOG("Fetch got %lu values for nodeIndex", static_cast<unsigned long>(self->_nodesWithAttributeInfo.count));
834874
#endif
835875

836-
if (![nodeIndex containsObject:nodeID]) {
876+
if (![self->_nodesWithAttributeInfo containsObject:nodeID]) {
837877
// Sanity check and delete if nodeID exists in index
838878
NSArray<NSNumber *> * endpointIndex = [self _fetchEndpointIndexForNodeID:nodeID];
839879
if (endpointIndex) {
@@ -1086,14 +1126,13 @@ - (void)storeClusterData:(NSDictionary<MTRClusterPath *, MTRDeviceClusterData *>
10861126
}
10871127

10881128
// Check if node index needs updating / creation
1089-
NSArray<NSNumber *> * nodeIndex = [self _fetchNodeIndex];
10901129
NSArray<NSNumber *> * nodeIndexToStore = nil;
1091-
if (!nodeIndex) {
1092-
// Ensure node index exists
1093-
MTR_LOG("No entry found for for nodeIndex - creating for node 0x%016llX", nodeID.unsignedLongLongValue);
1094-
nodeIndexToStore = [NSArray arrayWithObject:nodeID];
1095-
} else if (![nodeIndex containsObject:nodeID]) {
1096-
nodeIndexToStore = [nodeIndex arrayByAddingObject:nodeID];
1130+
{
1131+
std::lock_guard lock(self->_nodeArrayLock);
1132+
if (![self->_nodesWithAttributeInfo containsObject:nodeID]) {
1133+
[self->_nodesWithAttributeInfo addObject:nodeID];
1134+
nodeIndexToStore = [self->_nodesWithAttributeInfo copy];
1135+
}
10971136
}
10981137

10991138
if (nodeIndexToStore) {
@@ -1206,6 +1245,27 @@ - (void)synchronouslyPerformBlock:(void (^_Nullable)(void))block
12061245
});
12071246
}
12081247

1248+
- (NSArray<NSNumber *> *)nodesWithStoredData
1249+
{
1250+
// We have three types of stored data:
1251+
//
1252+
// 1) Attribute data
1253+
// 2) Session resumption data
1254+
// 3) Device data.
1255+
//
1256+
// Items 1 and 2 come with node indices. Item 3 does not, but in practice
1257+
// we should have device data if and only if we have attribute data for that
1258+
// node ID, barring odd error conditions.
1259+
//
1260+
// TODO: Consider changing how we store device data so we can easily recover
1261+
// the relevant set of node IDs.
1262+
NSMutableSet<NSNumber *> * nodeSet = [NSMutableSet set];
1263+
std::lock_guard lock(_nodeArrayLock);
1264+
[nodeSet addObjectsFromArray:_nodesWithResumptionInfo];
1265+
[nodeSet addObjectsFromArray:_nodesWithAttributeInfo];
1266+
return [nodeSet allObjects];
1267+
}
1268+
12091269
@end
12101270

12111271
@implementation MTRCASESessionResumptionInfo

src/darwin/Framework/CHIP/MTRDeviceController_Concrete.mm

+10
Original file line numberDiff line numberDiff line change
@@ -1720,6 +1720,16 @@ - (nullable NSNumber *)neededReadPrivilegeForClusterID:(NSNumber *)clusterID att
17201720
return nil;
17211721
}
17221722

1723+
- (NSArray<NSNumber *> *)nodesWithStoredData
1724+
{
1725+
if (!self.controllerDataStore) {
1726+
// We have nothing stored, if we have no way to store.
1727+
return @[];
1728+
}
1729+
1730+
return [self.controllerDataStore nodesWithStoredData];
1731+
}
1732+
17231733
@end
17241734

17251735
/**

src/darwin/Framework/CHIP/MTRDeviceController_XPC.mm

+5
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ @implementation MTRDeviceController_XPC
6464
: (NSNumber *) nodeID, deleteNodeID
6565
: (NSNumber *) nodeID)
6666

67+
MTR_DEVICECONTROLLER_SIMPLE_REMOTE_XPC_GETTER(nodesWithStoredData,
68+
NSArray<NSNumber *> *,
69+
@[], // Default return value
70+
getNodesWithStoredDataWithReply)
71+
6772
- (void)_updateRegistrationInfo
6873
{
6974
NSMutableDictionary * registrationInfo = [NSMutableDictionary dictionary];

src/darwin/Framework/CHIP/XPC Protocol/MTRXPCServerProtocol.h

+1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ MTR_AVAILABLE(ios(18.3), macos(15.3), watchos(11.3), tvos(18.3))
7777
- (oneway void)deviceController:(NSUUID *)controller unregisterNodeID:(NSNumber *)nodeID;
7878
- (oneway void)deviceController:(NSUUID *)controller updateControllerConfiguration:(NSDictionary *)controllerState MTR_AVAILABLE(ios(18.3), macos(15.3), watchos(11.3), tvos(18.3));
7979

80+
- (oneway void)deviceController:(NSUUID *)controller getNodesWithStoredDataWithReply:(void (^)(NSArray<NSNumber *> *))reply MTR_AVAILABLE(ios(18.4), macos(15.4), watchos(11.4), tvos(18.4));
8081
@end
8182

8283
MTR_AVAILABLE(ios(18.3), macos(15.3), watchos(11.3), tvos(18.3))

src/darwin/Framework/CHIPTests/MTRPerControllerStorageTests.m

+9
Original file line numberDiff line numberDiff line change
@@ -1521,6 +1521,8 @@ - (void)test008_TestDataStoreDirect
15211521
dispatch_sync(_storageQueue, ^{
15221522
[storageDelegate controller:controller storeValues:testBulkValues securityLevel:MTRStorageSecurityLevelSecure sharingType:MTRStorageSharingTypeNotShared];
15231523
});
1524+
// Since we messed with the node index, tell the data store to re-sync it's cache.
1525+
[controller.controllerDataStore unitTestRereadNodeIndex];
15241526
// Verify that the store resulted in the correct values
15251527
NSDictionary<MTRClusterPath *, MTRDeviceClusterData *> * dataStoreClusterData = [controller.controllerDataStore getStoredClusterDataForNodeID:@(3001)];
15261528
XCTAssertEqualObjects(dataStoreClusterData, bulkTestClusterDataDictionary);
@@ -1772,13 +1774,20 @@ - (void)test010_TestDataStoreMTRDeviceWithBulkReadWrite
17721774
XCTAssertNotNil([dataStore findResumptionInfoByNodeID:deviceID]);
17731775
XCTAssertNotNil([dataStore getStoredDeviceDataForNodeID:deviceID]);
17741776
XCTAssertNotNil([dataStore getStoredClusterDataForNodeID:deviceID]);
1777+
__auto_type * nodesWithStoredData = [controller nodesWithStoredData];
1778+
XCTAssertTrue([nodesWithStoredData containsObject:deviceID]);
1779+
XCTAssertEqualObjects(nodesWithStoredData, [dataStore nodesWithStoredData]);
1780+
XCTAssertEqualObjects(nodesWithStoredData, deviceAttributeCounts.allKeys);
17751781

17761782
[controller forgetDeviceWithNodeID:deviceID];
17771783
deviceAttributeCounts = [controller unitTestGetDeviceAttributeCounts];
17781784
XCTAssertNil(deviceAttributeCounts[deviceID]);
17791785
XCTAssertNil([dataStore findResumptionInfoByNodeID:deviceID]);
17801786
XCTAssertNil([dataStore getStoredDeviceDataForNodeID:deviceID]);
17811787
XCTAssertNil([dataStore getStoredClusterDataForNodeID:deviceID]);
1788+
nodesWithStoredData = [controller nodesWithStoredData];
1789+
XCTAssertFalse([nodesWithStoredData containsObject:deviceID]);
1790+
XCTAssertEqualObjects(nodesWithStoredData, [dataStore nodesWithStoredData]);
17821791

17831792
[controller shutdown];
17841793
XCTAssertFalse([controller isRunning]);

src/darwin/Framework/CHIPTests/TestHelpers/MTRTestDeclarations.h

+2
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,15 @@ NS_ASSUME_NONNULL_BEGIN
3636
- (void)clearStoredClusterDataForNodeID:(NSNumber *)nodeID;
3737
- (void)clearAllStoredClusterData;
3838
- (void)unitTestPruneEmptyStoredClusterDataBranches;
39+
- (void)unitTestRereadNodeIndex;
3940
- (NSString *)_endpointIndexKeyForNodeID:(NSNumber *)nodeID;
4041
- (NSString *)_clusterIndexKeyForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID;
4142
- (NSString *)_clusterDataKeyForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID;
4243
- (nullable NSArray<NSNumber *> *)_fetchEndpointIndexForNodeID:(NSNumber *)nodeID;
4344
- (nullable NSArray<NSNumber *> *)_fetchClusterIndexForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID;
4445
- (nullable MTRDeviceClusterData *)_fetchClusterDataForNodeID:(NSNumber *)nodeID endpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID;
4546
- (nullable NSDictionary<NSString *, id> *)getStoredDeviceDataForNodeID:(NSNumber *)nodeID;
47+
- (NSArray<NSNumber *> *)nodesWithStoredData;
4648
@end
4749

4850
// Declare internal methods for testing

0 commit comments

Comments
 (0)