diff --git a/src/darwin/Framework/CHIP/MTRDeviceController.mm b/src/darwin/Framework/CHIP/MTRDeviceController.mm index 44d3b63712d426..98d757d6d291e7 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceController.mm @@ -123,6 +123,8 @@ - (instancetype)initWithFactory:(MTRDeviceControllerFactory *)factory queue:(dispatch_queue_t)queue storageDelegate:(id _Nullable)storageDelegate storageDelegateQueue:(dispatch_queue_t _Nullable)storageDelegateQueue + otaProviderDelegate:(id _Nullable)otaProviderDelegate + otaProviderDelegateQueue:(dispatch_queue_t _Nullable)otaProviderDelegateQueue uniqueIdentifier:(NSUUID *)uniqueIdentifier { if (self = [super init]) { @@ -143,6 +145,58 @@ - (instancetype)initWithFactory:(MTRDeviceControllerFactory *)factory } } + // Ensure the otaProviderDelegate, if any, is valid. + if (otaProviderDelegate == nil && otaProviderDelegateQueue != nil) { + MTR_LOG_ERROR("Must have otaProviderDelegate when we have otaProviderDelegateQueue"); + return nil; + } + + if (otaProviderDelegate != nil && otaProviderDelegateQueue == nil) { + MTR_LOG_ERROR("Must have otaProviderDelegateQueue when we have otaProviderDelegate"); + return nil; + } + + if (otaProviderDelegate != nil) { + if (![otaProviderDelegate respondsToSelector:@selector(handleQueryImageForNodeID:controller:params:completion:)] + && ![otaProviderDelegate respondsToSelector:@selector(handleQueryImageForNodeID: + controller:params:completionHandler:)]) { + MTR_LOG_ERROR("Error: MTROTAProviderDelegate does not support handleQueryImageForNodeID"); + return nil; + } + if (![otaProviderDelegate respondsToSelector:@selector(handleApplyUpdateRequestForNodeID:controller:params:completion:)] + && ![otaProviderDelegate + respondsToSelector:@selector(handleApplyUpdateRequestForNodeID:controller:params:completionHandler:)]) { + MTR_LOG_ERROR("Error: MTROTAProviderDelegate does not support handleApplyUpdateRequestForNodeID"); + return nil; + } + if (![otaProviderDelegate respondsToSelector:@selector(handleNotifyUpdateAppliedForNodeID: + controller:params:completion:)] + && ![otaProviderDelegate + respondsToSelector:@selector(handleNotifyUpdateAppliedForNodeID:controller:params:completionHandler:)]) { + MTR_LOG_ERROR("Error: MTROTAProviderDelegate does not support handleNotifyUpdateAppliedForNodeID"); + return nil; + } + if (![otaProviderDelegate respondsToSelector:@selector + (handleBDXTransferSessionBeginForNodeID:controller:fileDesignator:offset:completion:)] + && ![otaProviderDelegate respondsToSelector:@selector + (handleBDXTransferSessionBeginForNodeID: + controller:fileDesignator:offset:completionHandler:)]) { + MTR_LOG_ERROR("Error: MTROTAProviderDelegate does not support handleBDXTransferSessionBeginForNodeID"); + return nil; + } + if (![otaProviderDelegate + respondsToSelector:@selector(handleBDXQueryForNodeID:controller:blockSize:blockIndex:bytesToSkip:completion:)] + && ![otaProviderDelegate + respondsToSelector:@selector(handleBDXQueryForNodeID: + controller:blockSize:blockIndex:bytesToSkip:completionHandler:)]) { + MTR_LOG_ERROR("Error: MTROTAProviderDelegate does not support handleBDXQueryForNodeID"); + return nil; + } + } + + _otaProviderDelegate = otaProviderDelegate; + _otaProviderDelegateQueue = otaProviderDelegateQueue; + _chipWorkQueue = queue; _factory = factory; _deviceMapLock = OS_UNFAIR_LOCK_INIT; diff --git a/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm b/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm index a11a7d30135e46..195fb61f5c7b6a 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm @@ -61,7 +61,6 @@ static NSString * const kErrorControllerFactoryInit = @"Init failure while initializing controller factory"; static NSString * const kErrorKeystoreInit = @"Init failure while initializing persistent storage keystore"; static NSString * const kErrorCertStoreInit = @"Init failure while initializing persistent storage operational certificate store"; -static NSString * const kErrorOtaProviderInit = @"Init failure while creating an OTA provider delegate"; static NSString * const kErrorSessionKeystoreInit = @"Init failure while initializing session keystore"; static bool sExitHandlerRegistered = false; @@ -123,6 +122,9 @@ @interface MTRDeviceControllerFactory () // D. Locking around reads not from the Matter queue is OK but not required. @property (nonatomic, readonly) os_unfair_lock controllersLock; +@property (nonatomic, readonly, nullable) id otaProviderDelegate; +@property (nonatomic, readonly, nullable) dispatch_queue_t otaProviderDelegateQueue; + - (BOOL)findMatchingFabric:(FabricTable &)fabricTable params:(MTRDeviceControllerStartupParams *)params fabric:(const FabricInfo * _Nullable * _Nonnull)fabric; @@ -289,9 +291,15 @@ - (void)cleanupInitObjects - (void)cleanupStartupObjects { - if (_otaProviderDelegateBridge) { - delete _otaProviderDelegateBridge; - _otaProviderDelegateBridge = nullptr; + // Make sure the deinit order here is the reverse of the init order in + // startControllerFactory: + _certificationDeclarationCertificates = nil; + _productAttestationAuthorityCertificates = nil; + + if (_opCertStore) { + _opCertStore->Finish(); + delete _opCertStore; + _opCertStore = nullptr; } if (_keystore) { @@ -300,11 +308,12 @@ - (void)cleanupStartupObjects _keystore = nullptr; } - if (_opCertStore) { - _opCertStore->Finish(); - delete _opCertStore; - _opCertStore = nullptr; + if (_otaProviderDelegateBridge) { + delete _otaProviderDelegateBridge; + _otaProviderDelegateBridge = nullptr; } + _otaProviderDelegateQueue = nil; + _otaProviderDelegate = nil; if (_sessionResumptionStorage) { delete _sessionResumptionStorage; @@ -412,57 +421,12 @@ - (BOOL)startControllerFactory:(MTRDeviceControllerFactoryParams *)startupParams return; } - if (startupParams.otaProviderDelegate) { - if (![startupParams.otaProviderDelegate respondsToSelector:@selector(handleQueryImageForNodeID: - controller:params:completion:)] - && ![startupParams.otaProviderDelegate - respondsToSelector:@selector(handleQueryImageForNodeID:controller:params:completionHandler:)]) { - MTR_LOG_ERROR("Error: MTROTAProviderDelegate does not support handleQueryImageForNodeID"); - errorCode = CHIP_ERROR_INVALID_ARGUMENT; - return; - } - if (![startupParams.otaProviderDelegate - respondsToSelector:@selector(handleApplyUpdateRequestForNodeID:controller:params:completion:)] - && ![startupParams.otaProviderDelegate - respondsToSelector:@selector(handleApplyUpdateRequestForNodeID:controller:params:completionHandler:)]) { - MTR_LOG_ERROR("Error: MTROTAProviderDelegate does not support handleApplyUpdateRequestForNodeID"); - errorCode = CHIP_ERROR_INVALID_ARGUMENT; - return; - } - if (![startupParams.otaProviderDelegate - respondsToSelector:@selector(handleNotifyUpdateAppliedForNodeID:controller:params:completion:)] - && ![startupParams.otaProviderDelegate - respondsToSelector:@selector(handleNotifyUpdateAppliedForNodeID:controller:params:completionHandler:)]) { - MTR_LOG_ERROR("Error: MTROTAProviderDelegate does not support handleNotifyUpdateAppliedForNodeID"); - errorCode = CHIP_ERROR_INVALID_ARGUMENT; - return; - } - if (![startupParams.otaProviderDelegate - respondsToSelector:@selector(handleBDXTransferSessionBeginForNodeID: - controller:fileDesignator:offset:completion:)] - && ![startupParams.otaProviderDelegate - respondsToSelector:@selector - (handleBDXTransferSessionBeginForNodeID:controller:fileDesignator:offset:completionHandler:)]) { - MTR_LOG_ERROR("Error: MTROTAProviderDelegate does not support handleBDXTransferSessionBeginForNodeID"); - errorCode = CHIP_ERROR_INVALID_ARGUMENT; - return; - } - if (![startupParams.otaProviderDelegate - respondsToSelector:@selector(handleBDXQueryForNodeID:controller:blockSize:blockIndex:bytesToSkip:completion:)] - && ![startupParams.otaProviderDelegate - respondsToSelector:@selector(handleBDXQueryForNodeID: - controller:blockSize:blockIndex:bytesToSkip:completionHandler:)]) { - MTR_LOG_ERROR("Error: MTROTAProviderDelegate does not support handleBDXQueryForNodeID"); - errorCode = CHIP_ERROR_INVALID_ARGUMENT; - return; - } - _otaProviderDelegateBridge = new MTROTAProviderDelegateBridge(startupParams.otaProviderDelegate); - if (_otaProviderDelegateBridge == nil) { - MTR_LOG_ERROR("Error: %@", kErrorOtaProviderInit); - errorCode = CHIP_ERROR_NO_MEMORY; - return; - } + _otaProviderDelegate = startupParams.otaProviderDelegate; + if (_otaProviderDelegate != nil) { + _otaProviderDelegateQueue = dispatch_queue_create( + "org.csa-iot.matter.framework.otaprovider.workqueue", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL); } + _otaProviderDelegateBridge = new MTROTAProviderDelegateBridge(); // TODO: Allow passing a different keystore implementation via startupParams. _keystore = new PersistentStorageOperationalKeystore(); @@ -594,19 +558,25 @@ - (MTRDeviceController * _Nullable)_startDeviceController:(id)startupParams return nil; } - id storageDelegate; - dispatch_queue_t storageDelegateQueue; + id _Nullable storageDelegate; + dispatch_queue_t _Nullable storageDelegateQueue; NSUUID * uniqueIdentifier; + id _Nullable otaProviderDelegate; + dispatch_queue_t _Nullable otaProviderDelegateQueue; if ([startupParams isKindOfClass:[MTRDeviceControllerStartupParameters class]]) { MTRDeviceControllerStartupParameters * params = startupParams; storageDelegate = params.storageDelegate; storageDelegateQueue = params.storageDelegateQueue; uniqueIdentifier = params.uniqueIdentifier; + otaProviderDelegate = params.otaProviderDelegate; + otaProviderDelegateQueue = params.otaProviderDelegateQueue; } else if ([startupParams isKindOfClass:[MTRDeviceControllerStartupParams class]]) { MTRDeviceControllerStartupParams * params = startupParams; storageDelegate = nil; storageDelegateQueue = nil; uniqueIdentifier = params.uniqueIdentifier; + otaProviderDelegate = nil; + otaProviderDelegateQueue = nil; } else { MTR_LOG_ERROR("Unknown kind of startup params: %@", startupParams); return nil; @@ -628,10 +598,19 @@ - (MTRDeviceController * _Nullable)_startDeviceController:(id)startupParams return nil; } + // Fall back to the factory-wide OTA provider delegate if one is not + // provided in the startup params. + if (otaProviderDelegate == nil) { + otaProviderDelegate = self.otaProviderDelegate; + otaProviderDelegateQueue = self.otaProviderDelegateQueue; + } + // Create the controller, so we start the event loop, since we plan to do // our fabric table operations there. auto * controller = [self _createController:storageDelegate storageDelegateQueue:storageDelegateQueue + otaProviderDelegate:otaProviderDelegate + otaProviderDelegateQueue:otaProviderDelegateQueue uniqueIdentifier:uniqueIdentifier]; if (controller == nil) { if (error != nil) { @@ -874,6 +853,8 @@ - (MTRDeviceController * _Nullable)createController:(MTRDeviceControllerStartupP - (MTRDeviceController * _Nullable)_createController:(id _Nullable)storageDelegate storageDelegateQueue:(dispatch_queue_t _Nullable)storageDelegateQueue + otaProviderDelegate:(id _Nullable)otaProviderDelegate + otaProviderDelegateQueue:(dispatch_queue_t _Nullable)otaProviderDelegateQueue uniqueIdentifier:(NSUUID *)uniqueIdentifier { [self _assertCurrentQueueIsNotMatterQueue]; @@ -882,6 +863,8 @@ - (MTRDeviceController * _Nullable)_createController:(id #import +#import NS_ASSUME_NONNULL_BEGIN @@ -59,6 +60,12 @@ MTR_NEWLY_AVAILABLE - (void)setOperationalCertificateIssuer:(id)operationalCertificateIssuer queue:(dispatch_queue_t)queue; +/** + * Set an MTROTAProviderDelegate to call (on the provided queue). Only needs to + * be called if this controller should be able to handle OTA for devices. + */ +- (void)setOTAProviderDelegate:(id)otaProviderDelegate queue:(dispatch_queue_t)queue; + @end MTR_NEWLY_AVAILABLE diff --git a/src/darwin/Framework/CHIP/MTRDeviceControllerStartupParams.mm b/src/darwin/Framework/CHIP/MTRDeviceControllerStartupParams.mm index 57c9270a682b08..7bbcea96183035 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceControllerStartupParams.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceControllerStartupParams.mm @@ -288,6 +288,13 @@ - (void)setOperationalCertificateIssuer:(id)ope _operationalCertificateIssuer = operationalCertificateIssuer; _operationalCertificateIssuerQueue = queue; } + +- (void)setOTAProviderDelegate:(id)otaProviderDelegate queue:(dispatch_queue_t)queue +{ + _otaProviderDelegate = otaProviderDelegate; + _otaProviderDelegateQueue = queue; +} + @end @implementation MTRDeviceControllerExternalCertificateStartupParameters diff --git a/src/darwin/Framework/CHIP/MTRDeviceControllerStartupParams_Internal.h b/src/darwin/Framework/CHIP/MTRDeviceControllerStartupParams_Internal.h index cb7540c2e16e65..734197ef72d2d6 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceControllerStartupParams_Internal.h +++ b/src/darwin/Framework/CHIP/MTRDeviceControllerStartupParams_Internal.h @@ -77,6 +77,9 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, strong, readonly) dispatch_queue_t storageDelegateQueue; @property (nonatomic, strong, readonly) NSUUID * uniqueIdentifier; +@property (nonatomic, strong, readonly, nullable) id otaProviderDelegate; +@property (nonatomic, strong, readonly, nullable) dispatch_queue_t otaProviderDelegateQueue; + @end MTR_HIDDEN diff --git a/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h b/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h index 89db3a9f951d3e..6ff5843f622513 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h +++ b/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h @@ -30,9 +30,10 @@ #import "MTRBaseDevice.h" #import "MTRDeviceController.h" #import "MTRDeviceControllerDataStore.h" -#import "MTRDeviceControllerStorageDelegate.h" #import +#import +#import @class MTRDeviceControllerStartupParamsInternal; @class MTRDeviceControllerFactory; @@ -75,12 +76,19 @@ NS_ASSUME_NONNULL_BEGIN * * This property MUST be gotten from the Matter work queue. */ -@property (readonly, nullable) NSNumber * compressedFabricID; +@property (nonatomic, readonly, nullable) NSNumber * compressedFabricID; /** * The per-controller data store this controller was initialized with, if any. */ -@property (nonatomic, nullable) MTRDeviceControllerDataStore * controllerDataStore; +@property (nonatomic, readonly, nullable) MTRDeviceControllerDataStore * controllerDataStore; + +/** + * OTA delegate and its queue, if this controller supports OTA. Either both + * will be non-nil or both will be nil. + */ +@property (nonatomic, readonly, nullable) id otaProviderDelegate; +@property (nonatomic, readonly, nullable) dispatch_queue_t otaProviderDelegateQueue; /** * Init a newly created controller. @@ -91,6 +99,8 @@ NS_ASSUME_NONNULL_BEGIN queue:(dispatch_queue_t)queue storageDelegate:(id _Nullable)storageDelegate storageDelegateQueue:(dispatch_queue_t _Nullable)storageDelegateQueue + otaProviderDelegate:(id _Nullable)otaProviderDelegate + otaProviderDelegateQueue:(dispatch_queue_t _Nullable)otaProviderDelegateQueue uniqueIdentifier:(NSUUID *)uniqueIdentifier; /** diff --git a/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.h b/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.h index f502dbff081d26..f74fb32a08bd19 100644 --- a/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.h +++ b/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.h @@ -24,7 +24,7 @@ NS_ASSUME_NONNULL_BEGIN class MTROTAProviderDelegateBridge : public chip::app::Clusters::OTAProviderDelegate { public: - MTROTAProviderDelegateBridge(id delegate); + MTROTAProviderDelegateBridge(); ~MTROTAProviderDelegateBridge(); CHIP_ERROR Init(chip::System::Layer * systemLayer, chip::Messaging::ExchangeManager * exchangeManager); @@ -65,9 +65,6 @@ class MTROTAProviderDelegateBridge : public chip::app::Clusters::OTAProviderDele static void ConvertToNotifyUpdateAppliedParams( const chip::app::Clusters::OtaSoftwareUpdateProvider::Commands::NotifyUpdateApplied::DecodableType & commandData, MTROTASoftwareUpdateProviderClusterNotifyUpdateAppliedParams * commandParams); - - _Nullable id mDelegate; - dispatch_queue_t mDelegateNotificationQueue; }; NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.mm b/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.mm index 4ebef0535253ee..1b9f436751b2a5 100644 --- a/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.mm +++ b/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.mm @@ -69,7 +69,6 @@ CHIP_ERROR PrepareForTransfer(FabricIndex fabricIndex, NodeId nodeId) { assertChipStackLockedByCurrentThread(); - VerifyOrReturnError(mDelegate != nil, CHIP_ERROR_INCORRECT_STATE); VerifyOrReturnError(mExchangeMgr != nullptr, CHIP_ERROR_INCORRECT_STATE); VerifyOrReturnError(mSystemLayer != nullptr, CHIP_ERROR_INCORRECT_STATE); @@ -121,18 +120,6 @@ void ControllerShuttingDown(MTRDeviceController * controller) } } - void SetDelegate(id delegate, dispatch_queue_t delegateNotificationQueue) - { - if (delegate) { - mDelegate = delegate; - mDelegateNotificationQueue = delegateNotificationQueue; - } else { - ResetState(); - mDelegate = nil; - mDelegateNotificationQueue = nil; - } - } - void ResetState() { assertChipStackLockedByCurrentThread(); @@ -161,6 +148,9 @@ void ResetState() mExchangeCtx = nullptr; } + mDelegate = nil; + mDelegateNotificationQueue = nil; + mInitialized = false; } @@ -463,6 +453,16 @@ CHIP_ERROR ConfigureState(chip::FabricIndex fabricIndex, chip::NodeId nodeId) ResetState(); } + auto * controller = [[MTRDeviceControllerFactory sharedInstance] runningControllerForFabricIndex:fabricIndex]; + VerifyOrReturnError(controller != nil, CHIP_ERROR_INCORRECT_STATE); + + mDelegate = controller.otaProviderDelegate; + mDelegateNotificationQueue = controller.otaProviderDelegateQueue; + + // We should have already checked that this controller supports OTA. + VerifyOrReturnError(mDelegate != nil, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mDelegateNotificationQueue != nil, CHIP_ERROR_INCORRECT_STATE); + // Start a timer to track whether we receive a BDX init after a successful query image in a reasonable amount of time CHIP_ERROR err = mSystemLayer->StartTimer(kBdxInitReceivedTimeout, HandleBdxInitReceivedTimeoutExpired, this); LogErrorOnFailure(err); @@ -497,18 +497,11 @@ CHIP_ERROR ConfigureState(chip::FabricIndex fabricIndex, chip::NodeId nodeId) NSInteger const kOtaProviderEndpoint = 0; } // anonymous namespace -MTROTAProviderDelegateBridge::MTROTAProviderDelegateBridge(id delegate) - : mDelegate(delegate) - , mDelegateNotificationQueue( - dispatch_queue_create("org.csa-iot.matter.framework.otaprovider.workqueue", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL)) -{ - gOtaSender.SetDelegate(delegate, mDelegateNotificationQueue); - Clusters::OTAProvider::SetDelegate(kOtaProviderEndpoint, this); -} +MTROTAProviderDelegateBridge::MTROTAProviderDelegateBridge() { Clusters::OTAProvider::SetDelegate(kOtaProviderEndpoint, this); } MTROTAProviderDelegateBridge::~MTROTAProviderDelegateBridge() { - gOtaSender.SetDelegate(nil, nil); + gOtaSender.ResetState(); Clusters::OTAProvider::SetDelegate(kOtaProviderEndpoint, nullptr); } @@ -547,6 +540,12 @@ bool GetPeerNodeInfo(CommandHandler * commandHandler, const ConcreteCommandPath return false; } + if (!controller.otaProviderDelegate) { + // This controller does not support OTA. + commandHandler->AddStatus(commandPath, Status::UnsupportedCommand); + return false; + } + *outController = controller; *outNodeId = desc.subject; return true; @@ -722,8 +721,8 @@ bool GetPeerNodeInfo(CommandHandler * commandHandler, const ConcreteCommandPath }]; }; - auto strongDelegate = mDelegate; - dispatch_async(mDelegateNotificationQueue, ^{ + auto strongDelegate = controller.otaProviderDelegate; + dispatch_async(controller.otaProviderDelegateQueue, ^{ if ([strongDelegate respondsToSelector:@selector(handleQueryImageForNodeID:controller:params:completion:)]) { [strongDelegate handleQueryImageForNodeID:@(nodeId) controller:controller @@ -782,8 +781,8 @@ bool GetPeerNodeInfo(CommandHandler * commandHandler, const ConcreteCommandPath auto * commandParams = [[MTROTASoftwareUpdateProviderClusterApplyUpdateRequestParams alloc] init]; ConvertToApplyUpdateRequestParams(commandData, commandParams); - auto strongDelegate = mDelegate; - dispatch_async(mDelegateNotificationQueue, ^{ + auto strongDelegate = controller.otaProviderDelegate; + dispatch_async(controller.otaProviderDelegateQueue, ^{ if ([strongDelegate respondsToSelector:@selector(handleApplyUpdateRequestForNodeID:controller:params:completion:)]) { [strongDelegate handleApplyUpdateRequestForNodeID:@(nodeId) controller:controller @@ -838,8 +837,8 @@ bool GetPeerNodeInfo(CommandHandler * commandHandler, const ConcreteCommandPath auto * commandParams = [[MTROTASoftwareUpdateProviderClusterNotifyUpdateAppliedParams alloc] init]; ConvertToNotifyUpdateAppliedParams(commandData, commandParams); - auto strongDelegate = mDelegate; - dispatch_async(mDelegateNotificationQueue, ^{ + auto strongDelegate = controller.otaProviderDelegate; + dispatch_async(controller.otaProviderDelegateQueue, ^{ if ([strongDelegate respondsToSelector:@selector(handleNotifyUpdateAppliedForNodeID:controller:params:completion:)]) { [strongDelegate handleNotifyUpdateAppliedForNodeID:@(nodeId) controller:controller