Darwin: Add delegate methods to populate initial state over new XPC (#35422)
* log actual class name in `description`
* add reg/unreg methods to server protocol
for device controllers to indicate interest/disinterest in node id
optional to avoid need to lockstep, will become non-optional in time
* protocol method to inform XPC MTRDevices of upstream internal state changes
optional to avoid need to lockstep, will become non-optional in time
* stub internal property to dictionary method
* add node ID as parameter for internal state updates
* initial work on private internal properties delegate
* sketch a way to expose key names to clients
* Restyled by clang-format
* Adding MTRDevice_XPC private method
* don't extern internal property keys
* move internal state keys to `MTRDevice_Internal`
where it is commonly accessible to both `_Concrete` and `_XPC`
* implement vid and pid accessors for internal state dictionary in `MTRDevice_XPC`
* prefix private method name with underscore
* Restyled by clang-format
* fix up preamble for new header
* Wrong key
* Should be static
---------
Co-authored-by: Restyled.io <commits@restyled.io>
Co-authored-by: Justin Wood <woody@apple.com>
diff --git a/src/darwin/Framework/CHIP/MTRDevice_Concrete.mm b/src/darwin/Framework/CHIP/MTRDevice_Concrete.mm
index 0739b05..8d531ff 100644
--- a/src/darwin/Framework/CHIP/MTRDevice_Concrete.mm
+++ b/src/darwin/Framework/CHIP/MTRDevice_Concrete.mm
@@ -254,6 +254,8 @@
@property (nonatomic) ReadClient * currentReadClient;
@property (nonatomic) SubscriptionCallback * currentSubscriptionCallback; // valid when and only when currentReadClient is valid
+@property (nonatomic, weak) id<MTRXPCClientProtocol_MTRDevice> privateInternalStateDelegate;
+
@end
// Declaring selector so compiler won't complain about testing and calling it in _handleReportEnd
@@ -461,7 +463,36 @@
}
return [NSString
- stringWithFormat:@"<MTRDevice: %p, XPC: NO, node: %016llX-%016llX (%llu), VID: %@, PID: %@, WiFi: %@, Thread: %@, state: %@, last subscription attempt wait: %lus, queued work: %lu, last report: %@%@, last subscription failure: %@%@, controller: %@>", self, _deviceController.compressedFabricID.unsignedLongLongValue, _nodeID.unsignedLongLongValue, _nodeID.unsignedLongLongValue, vid, pid, wifi, thread, InternalDeviceStateString(internalDeviceState), static_cast<unsigned long>(lastSubscriptionAttemptWait), static_cast<unsigned long>(_asyncWorkQueue.itemCount), mostRecentReportTime, reportAge, lastSubscriptionFailureTime, subscriptionFailureAge, _deviceController.uniqueIdentifier];
+ stringWithFormat:@"<%@: %p, node: %016llX-%016llX (%llu), VID: %@, PID: %@, WiFi: %@, Thread: %@, state: %@, last subscription attempt wait: %lus, queued work: %lu, last report: %@%@, last subscription failure: %@%@, controller: %@>", NSStringFromClass(self.class), self, _deviceController.compressedFabricID.unsignedLongLongValue, _nodeID.unsignedLongLongValue, _nodeID.unsignedLongLongValue, vid, pid, wifi, thread, InternalDeviceStateString(internalDeviceState), static_cast<unsigned long>(lastSubscriptionAttemptWait), static_cast<unsigned long>(_asyncWorkQueue.itemCount), mostRecentReportTime, reportAge, lastSubscriptionFailureTime, subscriptionFailureAge, _deviceController.uniqueIdentifier];
+}
+
+- (NSDictionary *)_internalProperties
+{
+ id vidOrUnknown, pidOrUnknown;
+
+ {
+ std::lock_guard lock(_descriptionLock);
+
+ vidOrUnknown = _vid ?: @"Unknown";
+ pidOrUnknown = _pid ?: @"Unknown";
+ // id networkFeatures = _allNetworkFeatures;
+ // id internalDeviceState = _internalDeviceStateForDescription;
+ // id lastSubscriptionAttemptWait = _lastSubscriptionAttemptWaitForDescription;
+ // id mostRecentReportTime = _mostRecentReportTimeForDescription;
+ // id lastSubscriptionFailureTime = _lastSubscriptionFailureTimeForDescription;
+ }
+
+ return @{
+ kMTRDeviceInternalPropertyKeyVendorID : vidOrUnknown,
+ kMTRDeviceInternalPropertyKeyProductID : pidOrUnknown,
+ };
+}
+
+- (void)_notifyPrivateInternalPropertiesDelegateOfChanges
+{
+ if ([self.privateInternalStateDelegate respondsToSelector:@selector(device:internalStateUpdated:)]) {
+ [self.privateInternalStateDelegate device:self.nodeID internalStateUpdated:[self _internalProperties]];
+ }
}
#pragma mark - Time Synchronization
@@ -951,6 +982,8 @@
}
}];
/* END DRAGONS */
+
+ [self _notifyPrivateInternalPropertiesDelegateOfChanges];
}
}
@@ -1151,7 +1184,7 @@
std::lock_guard lock(_descriptionLock);
_lastSubscriptionFailureTimeForDescription = _lastSubscriptionFailureTime;
}
-
+ [self _notifyPrivateInternalPropertiesDelegateOfChanges];
deviceUsesThread = [self _deviceUsesThread];
// If a previous resubscription failed, remove the item from the subscription pool.
@@ -1197,8 +1230,12 @@
os_unfair_lock_assert_owner(&_lock);
_lastSubscriptionAttemptWait = lastSubscriptionAttemptWait;
- std::lock_guard lock(_descriptionLock);
- _lastSubscriptionAttemptWaitForDescription = lastSubscriptionAttemptWait;
+ {
+ std::lock_guard lock(_descriptionLock);
+ _lastSubscriptionAttemptWaitForDescription = lastSubscriptionAttemptWait;
+ }
+
+ [self _notifyPrivateInternalPropertiesDelegateOfChanges];
}
- (void)_doHandleSubscriptionReset:(NSNumber * _Nullable)retryDelay
@@ -1214,6 +1251,7 @@
std::lock_guard lock(_descriptionLock);
_lastSubscriptionFailureTimeForDescription = _lastSubscriptionFailureTime;
}
+ [self _notifyPrivateInternalPropertiesDelegateOfChanges];
// if there is no delegate then also do not retry
if (![self _delegateExists]) {
@@ -1464,8 +1502,11 @@
{
_mostRecentReportTimes = mostRecentReportTimes;
- std::lock_guard lock(_descriptionLock);
- _mostRecentReportTimeForDescription = [mostRecentReportTimes lastObject];
+ {
+ std::lock_guard lock(_descriptionLock);
+ _mostRecentReportTimeForDescription = [mostRecentReportTimes lastObject];
+ }
+ [self _notifyPrivateInternalPropertiesDelegateOfChanges];
}
#endif
@@ -1524,6 +1565,7 @@
std::lock_guard lock(_descriptionLock);
_mostRecentReportTimeForDescription = [_mostRecentReportTimes lastObject];
}
+ [self _notifyPrivateInternalPropertiesDelegateOfChanges];
// Calculate running average and update multiplier - need at least 2 items to calculate intervals
if (_mostRecentReportTimes.count > 2) {
@@ -1594,6 +1636,8 @@
std::lock_guard lock(_descriptionLock);
_mostRecentReportTimeForDescription = nil;
}
+ [self _notifyPrivateInternalPropertiesDelegateOfChanges];
+
_deviceReportingExcessivelyStartTime = nil;
_reportToPersistenceDelayCurrentMultiplier = 1;
diff --git a/src/darwin/Framework/CHIP/MTRDevice_Internal.h b/src/darwin/Framework/CHIP/MTRDevice_Internal.h
index 20d9dd4..c3b6b60 100644
--- a/src/darwin/Framework/CHIP/MTRDevice_Internal.h
+++ b/src/darwin/Framework/CHIP/MTRDevice_Internal.h
@@ -204,6 +204,11 @@
@end
+#pragma mark - MTRDevice internal state monitoring
+@protocol MTRDeviceInternalStateDelegate
+- (void)devicePrivateInternalStateChanged:(MTRDevice *)device internalState:(NSDictionary *)state;
+@end
+
#pragma mark - Constants
static NSString * const kDefaultSubscriptionPoolSizeOverrideKey = @"subscriptionPoolSizeOverride";
@@ -217,4 +222,9 @@
// Declared inside platform, but noting here for reference
// static NSString * const kSRPTimeoutInMsecsUserDefaultKey = @"SRPTimeoutInMSecsOverride";
+// Concrete to XPC internal state property dictionary keys
+static NSString * const kMTRDeviceInternalPropertyKeyVendorID = @"MTRDeviceInternalStateKeyVendorID";
+static NSString * const kMTRDeviceInternalPropertyKeyProductID = @"MTRDeviceInternalStateKeyProductID";
+// TODO: more internal properties
+
NS_ASSUME_NONNULL_END
diff --git a/src/darwin/Framework/CHIP/MTRDevice_XPC.mm b/src/darwin/Framework/CHIP/MTRDevice_XPC.mm
index 85be0db..7267eb3 100644
--- a/src/darwin/Framework/CHIP/MTRDevice_XPC.mm
+++ b/src/darwin/Framework/CHIP/MTRDevice_XPC.mm
@@ -44,6 +44,7 @@
#import "MTRDeviceController_XPC.h"
#import "MTRDevice_Concrete.h"
#import "MTRDevice_Internal.h"
+#import "MTRDevice_XPC_Internal.h"
#import "MTRError_Internal.h"
#import "MTRKeypair.h"
#import "MTRLogging_Internal.h"
@@ -82,6 +83,8 @@
@implementation MTRDevice_XPC
+@synthesize _internalState;
+
- (instancetype)initWithNodeID:(NSNumber *)nodeID controller:(MTRDeviceController *)controller
{
// TODO: Verify that this is a valid MTRDeviceController_XPC?
@@ -93,6 +96,11 @@
return self;
}
+- (void)dealloc
+{
+ [self _setInternalState:nil];
+}
+
- (NSString *)description
{
// TODO: Figure out whether, and if so how, to log: VID, PID, WiFi, Thread,
@@ -100,7 +108,27 @@
// subscription attempt wait (does that apply to us?) queued work (do we
// have any?), last report, last subscription failure (does that apply to us?).
return [NSString
- stringWithFormat:@"<MTRDevice: %p, XPC: YES, node: %016llX-%016llX (%llu), controller: %@>", self, _deviceController.compressedFabricID.unsignedLongLongValue, _nodeID.unsignedLongLongValue, _nodeID.unsignedLongLongValue, _deviceController.uniqueIdentifier];
+ stringWithFormat:@"<%@: %p, node: %016llX-%016llX (%llu), VID: %@, PID: %@, controller: %@>", NSStringFromClass(self.class), self, _deviceController.compressedFabricID.unsignedLongLongValue, _nodeID.unsignedLongLongValue, _nodeID.unsignedLongLongValue, [self _vid], [self _pid], _deviceController.uniqueIdentifier];
+}
+
+- (nullable NSNumber *)_internalStateNumberOrNilForKey:(NSString *)key
+{
+ if ([_internalState[key] isKindOfClass:NSNumber.class]) {
+ NSNumber * number = _internalState[key];
+ return number;
+ } else {
+ return nil;
+ }
+}
+
+- (nullable NSNumber *)_vid
+{
+ return [self _internalStateNumberOrNilForKey:kMTRDeviceInternalPropertyKeyVendorID];
+}
+
+- (nullable NSNumber *)_pid
+{
+ return [self _internalStateNumberOrNilForKey:kMTRDeviceInternalPropertyKeyProductID];
}
#pragma mark - Client Callbacks (MTRDeviceDelegate)
diff --git a/src/darwin/Framework/CHIP/MTRDevice_XPC_Internal.h b/src/darwin/Framework/CHIP/MTRDevice_XPC_Internal.h
new file mode 100644
index 0000000..6cbc82d
--- /dev/null
+++ b/src/darwin/Framework/CHIP/MTRDevice_XPC_Internal.h
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2024 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.
+ */
+
+#import "MTRDevice_XPC.h"
+#import <Matter/Matter.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface MTRDevice_XPC ()
+@property (nullable, atomic, readwrite, retain, getter=_internalState, setter=_setInternalState:) NSDictionary * _internalState;
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/src/darwin/Framework/CHIP/XPC Protocol/MTRXPCClientProtocol.h b/src/darwin/Framework/CHIP/XPC Protocol/MTRXPCClientProtocol.h
index 3e276eb..b26b6d8 100644
--- a/src/darwin/Framework/CHIP/XPC Protocol/MTRXPCClientProtocol.h
+++ b/src/darwin/Framework/CHIP/XPC Protocol/MTRXPCClientProtocol.h
@@ -27,6 +27,10 @@
- (oneway void)deviceBecameActive:(NSNumber *)nodeID;
- (oneway void)deviceCachePrimed:(NSNumber *)nodeID;
- (oneway void)deviceConfigurationChanged:(NSNumber *)nodeID;
+
+@optional
+// temporarily optional to avoid lockstep needs
+- (oneway void)device:(NSNumber *)nodeID internalStateUpdated:(NSDictionary *)dictionary;
@end
MTR_NEWLY_AVAILABLE
diff --git a/src/darwin/Framework/CHIP/XPC Protocol/MTRXPCServerProtocol.h b/src/darwin/Framework/CHIP/XPC Protocol/MTRXPCServerProtocol.h
index 81d6ab9..1ebdea5 100644
--- a/src/darwin/Framework/CHIP/XPC Protocol/MTRXPCServerProtocol.h
+++ b/src/darwin/Framework/CHIP/XPC Protocol/MTRXPCServerProtocol.h
@@ -71,6 +71,11 @@
- (oneway void)deviceController:(NSUUID *)controller shutdownDeviceController:(NSUUID *)controller;
+@optional
+// register / unregister temporarily optional to avoid lockstep needs
+- (oneway void)deviceController:(NSUUID *)controller registerNodeID:(NSNumber *)nodeID;
+- (oneway void)deviceController:(NSUUID *)controller unregisterNodeID:(NSNumber *)nodeID;
+
@end
MTR_NEWLY_AVAILABLE
diff --git a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj
index 7d0ab04..69de347 100644
--- a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj
+++ b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj
@@ -379,6 +379,7 @@
B4FCD5722B603A6300832859 /* DownloadLogCommand.mm in Sources */ = {isa = PBXBuildFile; fileRef = B4FCD56F2B603A6300832859 /* DownloadLogCommand.mm */; };
B4FCD5732B611EB300832859 /* MTRDiagnosticLogsDownloader.h in Headers */ = {isa = PBXBuildFile; fileRef = B4C8E6B32B3453AD00FCD54D /* MTRDiagnosticLogsDownloader.h */; };
BA09EB43247477BA00605257 /* libCHIP.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BA09EB3F2474762900605257 /* libCHIP.a */; };
+ D4288E872C8A273F002FEC53 /* MTRDevice_XPC_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = D4288E862C8A273F002FEC53 /* MTRDevice_XPC_Internal.h */; };
D444F9A72C6E8F9D007761E5 /* MTRXPCServerProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D444F9A62C6E8F9D007761E5 /* MTRXPCServerProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; };
D444F9AA2C6E9A08007761E5 /* MTRXPCClientProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D444F9A82C6E99CA007761E5 /* MTRXPCClientProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; };
D4772A46285AE98400383630 /* MTRClusterConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = D4772A45285AE98300383630 /* MTRClusterConstants.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -823,6 +824,7 @@
B4FCD56F2B603A6300832859 /* DownloadLogCommand.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DownloadLogCommand.mm; sourceTree = "<group>"; };
BA09EB3F2474762900605257 /* libCHIP.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libCHIP.a; path = lib/libCHIP.a; sourceTree = BUILT_PRODUCTS_DIR; };
BA107AEE2470CFBB004287EB /* chip_xcode_build_connector.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = chip_xcode_build_connector.sh; sourceTree = "<group>"; };
+ D4288E862C8A273F002FEC53 /* MTRDevice_XPC_Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTRDevice_XPC_Internal.h; sourceTree = "<group>"; };
D437613E285BDC0D0051FEA2 /* MTRErrorTestUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRErrorTestUtils.h; sourceTree = "<group>"; };
D437613F285BDC0D0051FEA2 /* MTRTestKeys.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRTestKeys.h; sourceTree = "<group>"; };
D4376140285BDC0D0051FEA2 /* MTRTestStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRTestStorage.h; sourceTree = "<group>"; };
@@ -1332,6 +1334,7 @@
7596A84228762729004DAE0E /* MTRDevice.h */,
7596A84328762729004DAE0E /* MTRDevice.mm */,
9B5CCB5A2C6EC890009DD99B /* MTRDevice_XPC.h */,
+ D4288E862C8A273F002FEC53 /* MTRDevice_XPC_Internal.h */,
9B5CCB5B2C6EC890009DD99B /* MTRDevice_XPC.mm */,
9BDA2A072C5D9AFB00A32BDD /* MTRDevice_Concrete.h */,
9BDA2A052C5D9AF800A32BDD /* MTRDevice_Concrete.mm */,
@@ -1632,6 +1635,7 @@
5173A47729C0E2ED00F67F48 /* MTRFabricInfo.h in Headers */,
517BF3F0282B62B800A8B7DB /* MTRCertificates.h in Headers */,
51E51FBF282AD37A00FC978D /* MTRDeviceControllerStartupParams.h in Headers */,
+ D4288E872C8A273F002FEC53 /* MTRDevice_XPC_Internal.h in Headers */,
5136661628067D550025EDAE /* MTRDeviceControllerFactory.h in Headers */,
9B5CCB592C6E6FD3009DD99B /* MTRDeviceController_XPC_Internal.h in Headers */,
5178E6812AE098520069DF72 /* MTRCommandTimedCheck.h in Headers */,