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
{