Skip to content

Commit

Permalink
Add a suspend/resume API on MTRDeviceController. (#35434)
Browse files Browse the repository at this point in the history
This allows controllers to be suspended temporarily without having to shut them
down completely and restart them.
  • Loading branch information
bzbarsky-apple authored Sep 5, 2024
1 parent 7219ad2 commit ae41431
Show file tree
Hide file tree
Showing 9 changed files with 104 additions and 21 deletions.
23 changes: 23 additions & 0 deletions src/darwin/Framework/CHIP/MTRDeviceController.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1))
*/
@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.
*/
Expand Down Expand Up @@ -253,6 +258,24 @@ MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1))
error:(NSError * __autoreleasing *)error
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
Expand Down
41 changes: 38 additions & 3 deletions src/darwin/Framework/CHIP/MTRDeviceController.mm
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ @implementation MTRDeviceController {
MTRP256KeypairBridge _signingKeypairBridge;
MTRP256KeypairBridge _operationalKeypairBridge;

BOOL _suspended;

// Counters to track assertion status and access controlled by the _assertionLock
NSUInteger _keepRunningAssertionCounter;
BOOL _shutdownPending;
Expand All @@ -142,7 +144,7 @@ - (os_unfair_lock_t)deviceMapLock
return &_underlyingDeviceMapLock;
}

- (instancetype)initForSubclasses
- (instancetype)initForSubclasses:(BOOL)startSuspended
{
if (self = [super init]) {
// nothing, as superclass of MTRDeviceController is NSObject
Expand All @@ -153,15 +155,17 @@ - (instancetype)initForSubclasses
_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) {
Expand All @@ -184,6 +188,7 @@ - (instancetype)initWithFactory:(MTRDeviceControllerFactory *)factory
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,
Expand All @@ -195,6 +200,8 @@ - (instancetype)initWithFactory:(MTRDeviceControllerFactory *)factory
_shutdownPending = NO;
_assertionLock = OS_UNFAIR_LOCK_INIT;

_suspended = startSuspended;

if (storageDelegate != nil) {
if (storageDelegateQueue == nil) {
MTR_LOG_ERROR("storageDelegate provided without storageDelegateQueue");
Expand Down Expand Up @@ -331,6 +338,34 @@ - (BOOL)isRunning
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) {
Expand Down
5 changes: 4 additions & 1 deletion src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,7 @@ - (MTRDeviceController * _Nullable)_startDeviceController:(MTRDeviceController *
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;
Expand All @@ -483,6 +484,7 @@ - (MTRDeviceController * _Nullable)_startDeviceController:(MTRDeviceController *
otaProviderDelegateQueue = params.otaProviderDelegateQueue;
concurrentSubscriptionPoolSize = params.concurrentSubscriptionEstablishmentsAllowedOnThread;
storageBehaviorConfiguration = params.storageBehaviorConfiguration;
startSuspended = params.startSuspended;
} else if ([startupParams isKindOfClass:[MTRDeviceControllerStartupParams class]]) {
MTRDeviceControllerStartupParams * params = startupParams;
storageDelegate = nil;
Expand Down Expand Up @@ -545,7 +547,8 @@ - (MTRDeviceController * _Nullable)_startDeviceController:(MTRDeviceController *
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];
Expand Down
7 changes: 7 additions & 0 deletions src/darwin/Framework/CHIP/MTRDeviceControllerParameters.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ MTR_AVAILABLE(ios(17.6), macos(14.6), watchos(10.6), tvos(17.6))
@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

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,13 @@ - (instancetype)initWithOperationalKeypair:(id<MTRKeypair>)operationalKeypair
@implementation MTRDeviceControllerAbstractParameters
- (instancetype)_initInternal
{
return [super init];
if (!(self = [super init])) {
return nil;
}

_startSuspended = NO;

return self;
}
@end

Expand Down
3 changes: 2 additions & 1 deletion src/darwin/Framework/CHIP/MTRDeviceController_Concrete.mm
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,9 @@ - (instancetype)initWithFactory:(MTRDeviceControllerFactory *)factory
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;
Expand Down
5 changes: 3 additions & 2 deletions src/darwin/Framework/CHIP/MTRDeviceController_Internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ NS_ASSUME_NONNULL_BEGIN
// (moved here so subclasses can initialize differently)
@property (readwrite, retain) dispatch_queue_t chipWorkQueue;

- (instancetype)initForSubclasses;
- (instancetype)initForSubclasses:(BOOL)startSuspended;

#pragma mark - MTRDeviceControllerFactory methods

Expand Down Expand Up @@ -146,7 +146,8 @@ NS_ASSUME_NONNULL_BEGIN
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
Expand Down
1 change: 0 additions & 1 deletion src/darwin/Framework/CHIP/MTRDeviceController_XPC.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ NS_ASSUME_NONNULL_BEGIN

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
Expand Down
32 changes: 20 additions & 12 deletions src/darwin/Framework/CHIP/MTRDeviceController_XPC.mm
Original file line number Diff line number Diff line change
Expand Up @@ -83,17 +83,30 @@ - (NSXPCInterface *)_interfaceForClientProtocol
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;
}

Expand Down Expand Up @@ -131,7 +144,9 @@ - (id)initWithUniqueIdentifier:(NSUUID *)UUID xpConnectionBlock:(NSXPCConnection
#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;
Expand All @@ -155,13 +170,6 @@ - (id)initWithUniqueIdentifier:(NSUUID *)UUID machServiceName:(NSString *)machSe
}
#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
{
Expand Down

0 comments on commit ae41431

Please sign in to comment.