Add a suspend/resume API on MTRDeviceController. (#35434)

This allows controllers to be suspended temporarily without having to shut them
down completely and restart them.
diff --git a/src/darwin/Framework/CHIP/MTRDeviceController.h b/src/darwin/Framework/CHIP/MTRDeviceController.h
index 9e2c833..ad025c8 100644
--- a/src/darwin/Framework/CHIP/MTRDeviceController.h
+++ b/src/darwin/Framework/CHIP/MTRDeviceController.h
@@ -66,6 +66,11 @@
 @property (readonly, nonatomic, getter=isRunning) BOOL running;
 
 /**
+ * If true, the controller has been suspended via `suspend` and not resumed yet.
+ */
+@property (readonly, nonatomic, getter=isSuspended) BOOL suspended MTR_NEWLY_AVAILABLE;
+
+/**
  * The ID assigned to this controller at creation time.
  */
 @property (readonly, nonatomic) NSUUID * uniqueIdentifier MTR_AVAILABLE(ios(17.6), macos(14.6), watchos(10.6), tvos(17.6));
@@ -254,6 +259,24 @@
     MTR_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4));
 
 /**
+ * Suspend the controller.  This will attempt to stop all network traffic associated
+ * with the controller.  The controller will remain suspended until it is
+ * resumed.
+ *
+ * Suspending an already-suspended controller has no effect.
+ */
+- (void)suspend MTR_NEWLY_AVAILABLE;
+
+/**
+ * Resume the controller.  This has no effect if the controller is not
+ * suspended.
+ *
+ * A resume following any number of suspend calls will resume the controller;
+ * there does not need to be a resume call to match every suspend call.
+ */
+- (void)resume MTR_NEWLY_AVAILABLE;
+
+/**
  * Shut down the controller. Calls to shutdown after the first one are NO-OPs.
  * This must be called, either directly or via shutting down the
  * MTRDeviceControllerFactory, to avoid leaking the controller.
diff --git a/src/darwin/Framework/CHIP/MTRDeviceController.mm b/src/darwin/Framework/CHIP/MTRDeviceController.mm
index 650aba4..f6dc54f 100644
--- a/src/darwin/Framework/CHIP/MTRDeviceController.mm
+++ b/src/darwin/Framework/CHIP/MTRDeviceController.mm
@@ -131,6 +131,8 @@
     MTRP256KeypairBridge _signingKeypairBridge;
     MTRP256KeypairBridge _operationalKeypairBridge;
 
+    BOOL _suspended;
+
     // Counters to track assertion status and access controlled by the _assertionLock
     NSUInteger _keepRunningAssertionCounter;
     BOOL _shutdownPending;
@@ -142,7 +144,7 @@
     return &_underlyingDeviceMapLock;
 }
 
-- (instancetype)initForSubclasses
+- (instancetype)initForSubclasses:(BOOL)startSuspended
 {
     if (self = [super init]) {
         // nothing, as superclass of MTRDeviceController is NSObject
@@ -153,15 +155,17 @@
     _keepRunningAssertionCounter = 0;
     _shutdownPending = NO;
     _assertionLock = OS_UNFAIR_LOCK_INIT;
+
+    _suspended = startSuspended;
+
     return self;
 }
 
 - (nullable MTRDeviceController *)initWithParameters:(MTRDeviceControllerAbstractParameters *)parameters error:(NSError * __autoreleasing *)error
 {
     if ([parameters isKindOfClass:MTRXPCDeviceControllerParameters.class]) {
-        MTRXPCDeviceControllerParameters * resolvedParameters = (MTRXPCDeviceControllerParameters *) parameters;
         MTR_LOG("Starting up with XPC Device Controller Parameters: %@", parameters);
-        return [[MTRDeviceController_XPC alloc] initWithUniqueIdentifier:resolvedParameters.uniqueIdentifier xpConnectionBlock:resolvedParameters.xpcConnectionBlock];
+        return [[MTRDeviceController_XPC alloc] initWithParameters:parameters error:error];
     } else if (![parameters isKindOfClass:MTRDeviceControllerParameters.class]) {
         MTR_LOG_ERROR("Unsupported type of MTRDeviceControllerAbstractParameters: %@", parameters);
         if (error) {
@@ -184,6 +188,7 @@
                   uniqueIdentifier:(NSUUID *)uniqueIdentifier
     concurrentSubscriptionPoolSize:(NSUInteger)concurrentSubscriptionPoolSize
       storageBehaviorConfiguration:(MTRDeviceStorageBehaviorConfiguration *)storageBehaviorConfiguration
+                    startSuspended:(BOOL)startSuspended
 {
     if (self = [super init]) {
         // Make sure our storage is all set up to work as early as possible,
@@ -195,6 +200,8 @@
         _shutdownPending = NO;
         _assertionLock = OS_UNFAIR_LOCK_INIT;
 
+        _suspended = startSuspended;
+
         if (storageDelegate != nil) {
             if (storageDelegateQueue == nil) {
                 MTR_LOG_ERROR("storageDelegate provided without storageDelegateQueue");
@@ -331,6 +338,34 @@
     return _cppCommissioner != nullptr;
 }
 
+#pragma mark - Suspend/resume support
+
+- (BOOL)isSuspended
+{
+    return _suspended;
+}
+
+- (void)suspend
+{
+    _suspended = YES;
+
+    // TODO: In the concrete class (which is unused so far!), iterate our
+    // MTRDevices, tell them to tear down subscriptions.  Possibly close all
+    // CASE sessions for our identity.  Possibly try to see whether we can
+    // change our fabric entry to not advertise and restart advertising.
+
+    // TODO: What should happen with active commissioning sessions?  Presumably
+    // close them?
+}
+
+- (void)resume
+{
+    _suspended = NO;
+
+    // TODO: In the concrete class (which is unused so far!), iterate our
+    // MTRDevices, tell them to restart subscriptions.
+}
+
 - (BOOL)matchesPendingShutdownControllerWithOperationalCertificate:(nullable MTRCertificateDERBytes)operationalCertificate andRootCertificate:(nullable MTRCertificateDERBytes)rootCertificate
 {
     if (!operationalCertificate || !rootCertificate) {
diff --git a/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm b/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm
index 00b59f5..e0488f9 100644
--- a/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm
+++ b/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm
@@ -474,6 +474,7 @@
     dispatch_queue_t _Nullable otaProviderDelegateQueue;
     NSUInteger concurrentSubscriptionPoolSize = 0;
     MTRDeviceStorageBehaviorConfiguration * storageBehaviorConfiguration = nil;
+    BOOL startSuspended = NO;
     if ([startupParams isKindOfClass:[MTRDeviceControllerParameters class]]) {
         MTRDeviceControllerParameters * params = startupParams;
         storageDelegate = params.storageDelegate;
@@ -483,6 +484,7 @@
         otaProviderDelegateQueue = params.otaProviderDelegateQueue;
         concurrentSubscriptionPoolSize = params.concurrentSubscriptionEstablishmentsAllowedOnThread;
         storageBehaviorConfiguration = params.storageBehaviorConfiguration;
+        startSuspended = params.startSuspended;
     } else if ([startupParams isKindOfClass:[MTRDeviceControllerStartupParams class]]) {
         MTRDeviceControllerStartupParams * params = startupParams;
         storageDelegate = nil;
@@ -545,7 +547,8 @@
                     otaProviderDelegateQueue:otaProviderDelegateQueue
                             uniqueIdentifier:uniqueIdentifier
               concurrentSubscriptionPoolSize:concurrentSubscriptionPoolSize
-                storageBehaviorConfiguration:storageBehaviorConfiguration];
+                storageBehaviorConfiguration:storageBehaviorConfiguration
+                              startSuspended:startSuspended];
     if (controller == nil) {
         if (error != nil) {
             *error = [MTRError errorForCHIPErrorCode:CHIP_ERROR_INVALID_ARGUMENT];
diff --git a/src/darwin/Framework/CHIP/MTRDeviceControllerParameters.h b/src/darwin/Framework/CHIP/MTRDeviceControllerParameters.h
index caedc20..68d725f 100644
--- a/src/darwin/Framework/CHIP/MTRDeviceControllerParameters.h
+++ b/src/darwin/Framework/CHIP/MTRDeviceControllerParameters.h
@@ -31,6 +31,13 @@
 @interface MTRDeviceControllerAbstractParameters : NSObject
 - (instancetype)init NS_UNAVAILABLE;
 + (instancetype)new NS_UNAVAILABLE;
+
+/**
+ * Whether the controller should start out suspended.
+ *
+ * Defaults to NO.
+ */
+@property (nonatomic, assign) BOOL startSuspended;
 @end
 
 /**
diff --git a/src/darwin/Framework/CHIP/MTRDeviceControllerStartupParams.mm b/src/darwin/Framework/CHIP/MTRDeviceControllerStartupParams.mm
index 5850a8c..fa2791a 100644
--- a/src/darwin/Framework/CHIP/MTRDeviceControllerStartupParams.mm
+++ b/src/darwin/Framework/CHIP/MTRDeviceControllerStartupParams.mm
@@ -250,7 +250,13 @@
 @implementation MTRDeviceControllerAbstractParameters
 - (instancetype)_initInternal
 {
-    return [super init];
+    if (!(self = [super init])) {
+        return nil;
+    }
+
+    _startSuspended = NO;
+
+    return self;
 }
 @end
 
diff --git a/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.mm b/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.mm
index 0ea7bf9..6e7d056 100644
--- a/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.mm
+++ b/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.mm
@@ -190,8 +190,9 @@
                   uniqueIdentifier:(NSUUID *)uniqueIdentifier
     concurrentSubscriptionPoolSize:(NSUInteger)concurrentSubscriptionPoolSize
       storageBehaviorConfiguration:(MTRDeviceStorageBehaviorConfiguration *)storageBehaviorConfiguration
+                    startSuspended:(BOOL)startSuspended
 {
-    if (self = [super initForSubclasses]) {
+    if (self = [super initForSubclasses:startSuspended]) {
         // Make sure our storage is all set up to work as early as possible,
         // before we start doing anything else with the controller.
         _uniqueIdentifier = uniqueIdentifier;
diff --git a/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h b/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h
index f37d6fb..5d5acf8 100644
--- a/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h
+++ b/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h
@@ -73,7 +73,7 @@
 // (moved here so subclasses can initialize differently)
 @property (readwrite, retain) dispatch_queue_t chipWorkQueue;
 
-- (instancetype)initForSubclasses;
+- (instancetype)initForSubclasses:(BOOL)startSuspended;
 
 #pragma mark - MTRDeviceControllerFactory methods
 
@@ -146,7 +146,8 @@
           otaProviderDelegateQueue:(dispatch_queue_t _Nullable)otaProviderDelegateQueue
                   uniqueIdentifier:(NSUUID *)uniqueIdentifier
     concurrentSubscriptionPoolSize:(NSUInteger)concurrentSubscriptionPoolSize
-      storageBehaviorConfiguration:(MTRDeviceStorageBehaviorConfiguration *)storageBehaviorConfiguration;
+      storageBehaviorConfiguration:(MTRDeviceStorageBehaviorConfiguration *)storageBehaviorConfiguration
+                    startSuspended:(BOOL)startSuspended;
 
 /**
  * Check whether this controller is running on the given fabric, as represented
diff --git a/src/darwin/Framework/CHIP/MTRDeviceController_XPC.h b/src/darwin/Framework/CHIP/MTRDeviceController_XPC.h
index 574d3ea..c891c10 100644
--- a/src/darwin/Framework/CHIP/MTRDeviceController_XPC.h
+++ b/src/darwin/Framework/CHIP/MTRDeviceController_XPC.h
@@ -23,7 +23,6 @@
 
 MTR_TESTABLE
 @interface MTRDeviceController_XPC : MTRDeviceController <MTRXPCClientProtocol>
-- (id)initWithUniqueIdentifier:(NSUUID *)UUID xpConnectionBlock:(NSXPCConnection * (^)(void) )connectionBlock;
 #ifdef MTR_HAVE_MACH_SERVICE_NAME_CONSTRUCTOR
 - (id)initWithUniqueIdentifier:(NSUUID *)UUID machServiceName:(NSString *)machServiceName options:(NSXPCConnectionOptions)options
 #endif
diff --git a/src/darwin/Framework/CHIP/MTRDeviceController_XPC.mm b/src/darwin/Framework/CHIP/MTRDeviceController_XPC.mm
index d72dc21..1bf0888 100644
--- a/src/darwin/Framework/CHIP/MTRDeviceController_XPC.mm
+++ b/src/darwin/Framework/CHIP/MTRDeviceController_XPC.mm
@@ -83,17 +83,30 @@
     return [self.uniqueIdentifier UUIDString];
 }
 
-- (id)initWithUniqueIdentifier:(NSUUID *)UUID xpConnectionBlock:(NSXPCConnection * (^)(void) )connectionBlock
+- (nullable instancetype)initWithParameters:(MTRDeviceControllerAbstractParameters *)parameters
+                                      error:(NSError * __autoreleasing *)error
 {
-    if (self = [super initForSubclasses]) {
+    if (![parameters isKindOfClass:MTRXPCDeviceControllerParameters.class]) {
+        MTR_LOG_ERROR("Expected MTRXPCDeviceControllerParameters but got: %@", parameters);
+        if (error) {
+            *error = [MTRError errorForCHIPErrorCode:CHIP_ERROR_INVALID_ARGUMENT];
+        }
+        return nil;
+    }
+
+    if (self = [super initForSubclasses:parameters.startSuspended]) {
+        auto * xpcParameters = static_cast<MTRXPCDeviceControllerParameters *>(parameters);
+        auto connectionBlock = xpcParameters.xpcConnectionBlock;
+        auto * UUID = xpcParameters.uniqueIdentifier;
+
         MTR_LOG("Setting up XPC Controller for UUID: %@ with connection block: %p", UUID, connectionBlock);
 
         if (UUID == nil) {
-            MTR_LOG_ERROR("MTRDeviceController_XPC initWithUniqueIdentifier failed, nil UUID");
+            MTR_LOG_ERROR("MTRDeviceController_XPC initWithParameters failed, nil UUID");
             return nil;
         }
         if (connectionBlock == nil) {
-            MTR_LOG_ERROR("MTRDeviceController_XPC initWithUniqueIdentifier failed, nil connectionBlock");
+            MTR_LOG_ERROR("MTRDeviceController_XPC initWithParameters failed, nil connectionBlock");
             return nil;
         }
 
@@ -131,7 +144,9 @@
 #ifdef MTR_HAVE_MACH_SERVICE_NAME_CONSTRUCTOR
 - (id)initWithUniqueIdentifier:(NSUUID *)UUID machServiceName:(NSString *)machServiceName options:(NSXPCConnectionOptions)options
 {
-    if (self = [super initForSubclasses]) {
+    // TODO: Presumably this should end up doing some sort of
+    // MTRDeviceControllerAbstractParameters thing eventually?
+    if (self = [super initForSubclasses:NO]) {
         MTR_LOG("Setting up XPC Controller for UUID: %@  with machServiceName: %s options: %d", UUID, machServiceName, options);
         self.xpcConnection = [[NSXPCConnection alloc] initWithMachServiceName:machServiceName options:options];
         self.uniqueIdentifier = UUID;
@@ -155,13 +170,6 @@
 }
 #endif // MTR_HAVE_MACH_SERVICE_NAME_CONSTRUCTOR
 
-- (nullable instancetype)initWithParameters:(MTRDeviceControllerAbstractParameters *)parameters
-                                      error:(NSError * __autoreleasing *)error
-{
-    MTR_LOG_ERROR("%s: unimplemented method called", __PRETTY_FUNCTION__);
-    return nil;
-}
-
 // If prefetchedClusterData is not provided, load attributes individually from controller data store
 - (MTRDevice *)_setupDeviceForNodeID:(NSNumber *)nodeID prefetchedClusterData:(NSDictionary<MTRClusterPath *, MTRDeviceClusterData *> *)prefetchedClusterData
 {