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 */,