Make sure to not dispatch to shut-down queues in MTROTAProviderDelegateBridge. (#23841)

* Make sure we call our delegate callbacks on a queue different from the Matter
  work queue.
* Assert that we are on the Matter work queue in all the places where we should
  be.
* Make sure to dispatch to the Matter work queue via the controller we were
  dealing with, so dispatch does not happen if that controller has shut down.
* Reset OTA transfers when the controller the transfer is associated with shuts
  down.
* Ensure that async callbacks for a stale transfer don't affect a current
  transfer.

Fixes https://github.com/project-chip/connectedhomeip/issues/22541
diff --git a/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.h b/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.h
index 97e7ca2..2169aed 100644
--- a/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.h
+++ b/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.h
@@ -44,6 +44,9 @@
 /*
  * OTA Provider delegate to be called when an OTA Requestor is requesting a software update.
  * Defaults to nil.
+ *
+ * Calls to this delegate can happen on an arbitrary thread, but will not happen
+ * concurrently.
  */
 @property (nonatomic, strong, nullable) id<MTROTAProviderDelegate> otaProviderDelegate;
 
diff --git a/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm b/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm
index 81f53c4..f089a00 100644
--- a/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm
+++ b/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm
@@ -674,8 +674,11 @@
     VerifyOrReturnValue(_otaProviderDelegateBridge != nil, controller);
     VerifyOrReturnValue([_controllers count] == 1, controller);
 
-    auto systemState = _controllerFactory->GetSystemState();
-    CHIP_ERROR err = _otaProviderDelegateBridge->Init(systemState->SystemLayer(), systemState->ExchangeMgr());
+    __block CHIP_ERROR err;
+    dispatch_sync(_chipWorkQueue, ^{
+        auto systemState = _controllerFactory->GetSystemState();
+        err = _otaProviderDelegateBridge->Init(systemState->SystemLayer(), systemState->ExchangeMgr());
+    });
     if (CHIP_NO_ERROR != err) {
         MTR_LOG_ERROR("Failed to init provider delegate bridge: %" CHIP_ERROR_FORMAT, err.Format());
         [controller shutdown];
@@ -711,19 +714,23 @@
     [_controllers removeObject:controller];
 
     if ([_controllers count] == 0) {
-        if (_otaProviderDelegateBridge) {
-            _otaProviderDelegateBridge->Shutdown();
-        }
-
         // That was our last controller.  Stop the event loop before it
         // shuts down, because shutdown of the last controller will tear
         // down most of the world.
         DeviceLayer::PlatformMgrImpl().StopEventLoopTask();
 
+        if (_otaProviderDelegateBridge) {
+            _otaProviderDelegateBridge->Shutdown();
+        }
+
         [controller shutDownCppController];
     } else {
         // Do the controller shutdown on the Matter work queue.
         dispatch_sync(_chipWorkQueue, ^{
+            if (_otaProviderDelegateBridge) {
+                _otaProviderDelegateBridge->ControllerShuttingDown(controller);
+            }
+
             [controller shutDownCppController];
         });
     }
diff --git a/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.h b/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.h
index 3c69407..a4b69f2 100644
--- a/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.h
+++ b/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.h
@@ -28,8 +28,15 @@
     ~MTROTAProviderDelegateBridge();
 
     CHIP_ERROR Init(chip::System::Layer * systemLayer, chip::Messaging::ExchangeManager * exchangeManager);
+
+    // Shutdown must be called after the event loop has been stopped, since it
+    // touches Matter objects.
     void Shutdown();
 
+    // ControllerShuttingDown must be called on the Matter work queue, since it
+    // touches Matter objects.
+    void ControllerShuttingDown(MTRDeviceController * controller);
+
     void HandleQueryImage(
         chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath,
         const chip::app::Clusters::OtaSoftwareUpdateProvider::Commands::QueryImage::DecodableType & commandData) override;
@@ -60,7 +67,7 @@
         MTROTASoftwareUpdateProviderClusterNotifyUpdateAppliedParams * commandParams);
 
     _Nullable id<MTROTAProviderDelegate> mDelegate;
-    dispatch_queue_t mWorkQueue;
+    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 5de6041..7d5e8c1 100644
--- a/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.mm
+++ b/src/darwin/Framework/CHIP/MTROTAProviderDelegateBridge.mm
@@ -17,16 +17,19 @@
 
 #import "MTROTAProviderDelegateBridge.h"
 #import "MTRDeviceControllerFactory_Internal.h"
+#import "MTRDeviceController_Internal.h"
 #import "NSDataSpanConversion.h"
 #import "NSStringSpanConversion.h"
 
 #include <app/clusters/ota-provider/ota-provider.h>
+#include <controller/CHIPDeviceController.h>
 #include <lib/support/TypeTraits.h>
 #include <platform/PlatformManager.h>
 #include <protocols/interaction_model/Constants.h>
 
 #include <MTRError_Internal.h>
 #include <messaging/ExchangeMgr.h>
+#include <platform/LockTracker.h>
 #include <protocols/bdx/BdxUri.h>
 #include <protocols/bdx/TransferFacilitator.h>
 
@@ -49,6 +52,8 @@
 
     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);
@@ -61,6 +66,8 @@
 
     CHIP_ERROR Init(System::Layer * systemLayer, Messaging::ExchangeManager * exchangeMgr)
     {
+        assertChipStackLockedByCurrentThread();
+
         VerifyOrReturnError(mSystemLayer == nullptr, CHIP_ERROR_INCORRECT_STATE);
         VerifyOrReturnError(mExchangeMgr == nullptr, CHIP_ERROR_INCORRECT_STATE);
         VerifyOrReturnError(systemLayer != nullptr, CHIP_ERROR_INCORRECT_STATE);
@@ -70,13 +77,14 @@
 
         mSystemLayer = systemLayer;
         mExchangeMgr = exchangeMgr;
-        mWorkQueue = DeviceLayer::PlatformMgrImpl().GetWorkQueue();
 
         return CHIP_NO_ERROR;
     }
 
     CHIP_ERROR Shutdown()
     {
+        assertChipStackLockedByCurrentThread();
+
         VerifyOrReturnError(mSystemLayer != nullptr, CHIP_ERROR_INCORRECT_STATE);
         VerifyOrReturnError(mExchangeMgr != nullptr, CHIP_ERROR_INCORRECT_STATE);
 
@@ -84,25 +92,39 @@
 
         mExchangeMgr = nullptr;
         mSystemLayer = nullptr;
-        mWorkQueue = nil;
+        mDelegateNotificationQueue = nil;
 
         ResetState();
 
         return CHIP_NO_ERROR;
     }
 
-    void SetDelegate(id<MTROTAProviderDelegate> delegate)
+    void ControllerShuttingDown(MTRDeviceController * controller)
+    {
+        assertChipStackLockedByCurrentThread();
+
+        if (mInitialized && mFabricIndex.Value() == controller.fabricIndex) {
+            ResetState();
+        }
+    }
+
+    void SetDelegate(id<MTROTAProviderDelegate> delegate, dispatch_queue_t delegateNotificationQueue)
     {
         if (delegate) {
             mDelegate = delegate;
+            mDelegateNotificationQueue = delegateNotificationQueue;
         } else {
             ResetState();
+            mDelegate = nil;
+            mDelegateNotificationQueue = nil;
         }
     }
 
 private:
     CHIP_ERROR OnMessageToSend(TransferSession::OutputEvent & event)
     {
+        assertChipStackLockedByCurrentThread();
+
         VerifyOrReturnError(mExchangeCtx != nullptr, CHIP_ERROR_INCORRECT_STATE);
         VerifyOrReturnError(mDelegate != nil, CHIP_ERROR_INCORRECT_STATE);
 
@@ -120,6 +142,8 @@
 
     CHIP_ERROR OnTransferSessionBegin(TransferSession::OutputEvent & event)
     {
+        assertChipStackLockedByCurrentThread();
+
         VerifyOrReturnError(mFabricIndex.HasValue(), CHIP_ERROR_INCORRECT_STATE);
         VerifyOrReturnError(mNodeId.HasValue(), CHIP_ERROR_INCORRECT_STATE);
         uint16_t fdl = 0;
@@ -128,33 +152,48 @@
 
         auto fileDesignator = [[NSString alloc] initWithBytes:fd length:fdl encoding:NSUTF8StringEncoding];
         auto offset = @(mTransfer.GetStartOffset());
-        auto completionHandler = ^(NSError * _Nullable error) {
-            dispatch_async(mWorkQueue, ^{
-                if (error != nil) {
-                    LogErrorOnFailure([MTRError errorToCHIPErrorCode:error]);
-                    LogErrorOnFailure(mTransfer.AbortTransfer(bdx::StatusCode::kUnknown));
-                    return;
-                }
-
-                // bdx::TransferSession will automatically reject a transfer if there are no
-                // common supported control modes. It will also default to the smaller
-                // block size.
-                TransferSession::TransferAcceptData acceptData;
-                acceptData.ControlMode = bdx::TransferControlFlags::kReceiverDrive;
-                acceptData.MaxBlockSize = mTransfer.GetTransferBlockSize();
-                acceptData.StartOffset = mTransfer.GetStartOffset();
-                acceptData.Length = mTransfer.GetTransferLength();
-
-                LogErrorOnFailure(mTransfer.AcceptTransfer(acceptData));
-            });
-        };
 
         auto * controller = [[MTRDeviceControllerFactory sharedInstance] runningControllerForFabricIndex:mFabricIndex.Value()];
         VerifyOrReturnError(controller != nil, CHIP_ERROR_INCORRECT_STATE);
+
+        auto transferGeneration = mTransferGeneration;
+
+        auto completionHandler = ^(NSError * _Nullable error) {
+            [controller
+                asyncDispatchToMatterQueue:^(chip::Controller::DeviceCommissioner *) {
+                    assertChipStackLockedByCurrentThread();
+
+                    if (!mInitialized || mTransferGeneration != transferGeneration) {
+                        // Callback for a stale transfer.
+                        return;
+                    }
+
+                    if (error != nil) {
+                        LogErrorOnFailure([MTRError errorToCHIPErrorCode:error]);
+                        LogErrorOnFailure(mTransfer.AbortTransfer(bdx::StatusCode::kUnknown));
+                        return;
+                    }
+
+                    // bdx::TransferSession will automatically reject a transfer if there are no
+                    // common supported control modes. It will also default to the smaller
+                    // block size.
+                    TransferSession::TransferAcceptData acceptData;
+                    acceptData.ControlMode = bdx::TransferControlFlags::kReceiverDrive;
+                    acceptData.MaxBlockSize = mTransfer.GetTransferBlockSize();
+                    acceptData.StartOffset = mTransfer.GetStartOffset();
+                    acceptData.Length = mTransfer.GetTransferLength();
+
+                    LogErrorOnFailure(mTransfer.AcceptTransfer(acceptData));
+                }
+                              errorHandler:^(NSError *) {
+                                  // Not much we can do here
+                              }];
+        };
+
         auto nodeId = @(mNodeId.Value());
 
         auto strongDelegate = mDelegate;
-        dispatch_async(mWorkQueue, ^{
+        dispatch_async(mDelegateNotificationQueue, ^{
             if ([strongDelegate respondsToSelector:@selector
                                 (handleBDXTransferSessionBeginForNodeID:controller:fileDesignator:offset:completion:)]) {
                 [strongDelegate handleBDXTransferSessionBeginForNodeID:nodeId
@@ -176,6 +215,8 @@
 
     CHIP_ERROR OnTransferSessionEnd(TransferSession::OutputEvent & event)
     {
+        assertChipStackLockedByCurrentThread();
+
         VerifyOrReturnError(mFabricIndex.HasValue(), CHIP_ERROR_INCORRECT_STATE);
         VerifyOrReturnError(mNodeId.HasValue(), CHIP_ERROR_INCORRECT_STATE);
 
@@ -191,7 +232,7 @@
         auto nodeId = @(mNodeId.Value());
 
         auto strongDelegate = mDelegate;
-        dispatch_async(mWorkQueue, ^{
+        dispatch_async(mDelegateNotificationQueue, ^{
             [strongDelegate handleBDXTransferSessionEndForNodeID:nodeId
                                                       controller:controller
                                                            error:[MTRError errorForCHIPErrorCode:error]];
@@ -203,6 +244,8 @@
 
     CHIP_ERROR OnBlockQuery(TransferSession::OutputEvent & event)
     {
+        assertChipStackLockedByCurrentThread();
+
         VerifyOrReturnError(mFabricIndex.HasValue(), CHIP_ERROR_INCORRECT_STATE);
         VerifyOrReturnError(mNodeId.HasValue(), CHIP_ERROR_INCORRECT_STATE);
 
@@ -214,34 +257,48 @@
             bytesToSkip = @(event.bytesToSkip.BytesToSkip);
         }
 
+        auto * controller = [[MTRDeviceControllerFactory sharedInstance] runningControllerForFabricIndex:mFabricIndex.Value()];
+        VerifyOrReturnError(controller != nil, CHIP_ERROR_INCORRECT_STATE);
+
+        auto transferGeneration = mTransferGeneration;
+
         auto completionHandler = ^(NSData * _Nullable data, BOOL isEOF) {
-            dispatch_async(mWorkQueue, ^{
-                if (data == nil) {
-                    LogErrorOnFailure(mTransfer.AbortTransfer(bdx::StatusCode::kUnknown));
-                    return;
-                }
+            [controller
+                asyncDispatchToMatterQueue:^(chip::Controller::DeviceCommissioner *) {
+                    assertChipStackLockedByCurrentThread();
 
-                TransferSession::BlockData blockData;
-                blockData.Data = static_cast<const uint8_t *>([data bytes]);
-                blockData.Length = static_cast<size_t>([data length]);
-                blockData.IsEof = isEOF;
+                    if (!mInitialized || mTransferGeneration != transferGeneration) {
+                        // Callback for a stale transfer.
+                        return;
+                    }
 
-                CHIP_ERROR err = mTransfer.PrepareBlock(blockData);
-                if (CHIP_NO_ERROR != err) {
-                    LogErrorOnFailure(err);
-                    LogErrorOnFailure(mTransfer.AbortTransfer(bdx::StatusCode::kUnknown));
+                    if (data == nil) {
+                        LogErrorOnFailure(mTransfer.AbortTransfer(bdx::StatusCode::kUnknown));
+                        return;
+                    }
+
+                    TransferSession::BlockData blockData;
+                    blockData.Data = static_cast<const uint8_t *>([data bytes]);
+                    blockData.Length = static_cast<size_t>([data length]);
+                    blockData.IsEof = isEOF;
+
+                    CHIP_ERROR err = mTransfer.PrepareBlock(blockData);
+                    if (CHIP_NO_ERROR != err) {
+                        LogErrorOnFailure(err);
+                        LogErrorOnFailure(mTransfer.AbortTransfer(bdx::StatusCode::kUnknown));
+                    }
                 }
-            });
+                              errorHandler:^(NSError *) {
+                                  // Not much we can do here
+                              }];
         };
 
         // TODO Handle MaxLength
 
-        auto * controller = [[MTRDeviceControllerFactory sharedInstance] runningControllerForFabricIndex:mFabricIndex.Value()];
-        VerifyOrReturnError(controller != nil, CHIP_ERROR_INCORRECT_STATE);
         auto nodeId = @(mNodeId.Value());
 
         auto strongDelegate = mDelegate;
-        dispatch_async(mWorkQueue, ^{
+        dispatch_async(mDelegateNotificationQueue, ^{
             if ([strongDelegate respondsToSelector:@selector
                                 (handleBDXQueryForNodeID:controller:blockSize:blockIndex:bytesToSkip:completion:)]) {
                 [strongDelegate handleBDXQueryForNodeID:nodeId
@@ -303,6 +360,8 @@
 
     CHIP_ERROR ConfigureState(chip::FabricIndex fabricIndex, chip::NodeId nodeId)
     {
+        assertChipStackLockedByCurrentThread();
+
         if (mInitialized) {
             // Prevent a new node connection since another is active.
             VerifyOrReturnError(mFabricIndex.Value() == fabricIndex && mNodeId.Value() == nodeId, CHIP_ERROR_BUSY);
@@ -321,11 +380,14 @@
 
     void ResetState()
     {
+        assertChipStackLockedByCurrentThread();
+
         if (!mInitialized) {
             return;
         }
 
         Responder::ResetTransfer();
+        ++mTransferGeneration;
         mFabricIndex.ClearValue();
         mNodeId.ClearValue();
 
@@ -341,8 +403,14 @@
     Optional<FabricIndex> mFabricIndex;
     Optional<NodeId> mNodeId;
     id<MTROTAProviderDelegate> mDelegate = nil;
-    dispatch_queue_t mWorkQueue = nil;
+    dispatch_queue_t mDelegateNotificationQueue = nil;
     Messaging::ExchangeManager * mExchangeMgr = nullptr;
+
+    // Since we are a singleton, we get reused across transfers, but also have
+    // async calls that can happen.  The transfer generation keeps track of
+    // which transfer we are currently doing, so we can ignore async calls
+    // attached to no-longer-running transfers.
+    uint64_t mTransferGeneration = 0;
 };
 
 BdxOTASender gOtaSender;
@@ -351,15 +419,15 @@
 
 MTROTAProviderDelegateBridge::MTROTAProviderDelegateBridge(id<MTROTAProviderDelegate> delegate)
     : mDelegate(delegate)
-    , mWorkQueue(DeviceLayer::PlatformMgrImpl().GetWorkQueue())
+    , mDelegateNotificationQueue(dispatch_queue_create("com.csa.matter.framework.otaprovider.workqueue", DISPATCH_QUEUE_SERIAL))
 {
-    gOtaSender.SetDelegate(delegate);
+    gOtaSender.SetDelegate(delegate, mDelegateNotificationQueue);
     Clusters::OTAProvider::SetDelegate(kOtaProviderEndpoint, this);
 }
 
 MTROTAProviderDelegateBridge::~MTROTAProviderDelegateBridge()
 {
-    gOtaSender.SetDelegate(nil);
+    gOtaSender.SetDelegate(nil, nil);
     Clusters::OTAProvider::SetDelegate(kOtaProviderEndpoint, nullptr);
 }
 
@@ -370,6 +438,11 @@
 
 void MTROTAProviderDelegateBridge::Shutdown() { gOtaSender.Shutdown(); }
 
+void MTROTAProviderDelegateBridge::ControllerShuttingDown(MTRDeviceController * controller)
+{
+    gOtaSender.ControllerShuttingDown(controller);
+}
+
 namespace {
 // Return false if we could not get peer node info (a running controller for
 // the fabric and a node id).  In that case we will have already added an
@@ -456,6 +529,8 @@
 void MTROTAProviderDelegateBridge::HandleQueryImage(
     CommandHandler * commandObj, const ConcreteCommandPath & commandPath, const Commands::QueryImage::DecodableType & commandData)
 {
+    assertChipStackLockedByCurrentThread();
+
     NodeId nodeId;
     MTRDeviceController * controller;
     if (!GetPeerNodeInfo(commandObj, commandPath, &nodeId, &controller)) {
@@ -473,58 +548,66 @@
     __block CommandHandler::Handle handle(commandObj);
     __block ConcreteCommandPath cachedCommandPath(commandPath.mEndpointId, commandPath.mClusterId, commandPath.mCommandId);
 
-    auto completionHandler = ^(
-        MTROTASoftwareUpdateProviderClusterQueryImageResponseParams * _Nullable data, NSError * _Nullable error) {
-        dispatch_async(mWorkQueue, ^{
-            CommandHandler * handler = EnsureValidState(handle, cachedCommandPath, "QueryImage", data, error);
-            VerifyOrReturn(handler != nullptr);
+    auto completionHandler
+        = ^(MTROTASoftwareUpdateProviderClusterQueryImageResponseParams * _Nullable data, NSError * _Nullable error) {
+              [controller
+                  asyncDispatchToMatterQueue:^(chip::Controller::DeviceCommissioner *) {
+                      assertChipStackLockedByCurrentThread();
 
-            ChipLogDetail(Controller, "QueryImage: application responded with: %s",
-                [[data description] cStringUsingEncoding:NSUTF8StringEncoding]);
+                      CommandHandler * handler = EnsureValidState(handle, cachedCommandPath, "QueryImage", data, error);
+                      VerifyOrReturn(handler != nullptr);
 
-            Commands::QueryImageResponse::Type response;
-            ConvertFromQueryImageResponseParms(data, response);
+                      ChipLogDetail(Controller, "QueryImage: application responded with: %s",
+                          [[data description] cStringUsingEncoding:NSUTF8StringEncoding]);
 
-            auto hasUpdate = [data.status isEqual:@(MTROTASoftwareUpdateProviderOTAQueryStatusUpdateAvailable)];
-            auto isBDXProtocolSupported =
-                [commandParams.protocolsSupported containsObject:@(MTROTASoftwareUpdateProviderOTADownloadProtocolBDXSynchronous)];
+                      Commands::QueryImageResponse::Type response;
+                      ConvertFromQueryImageResponseParms(data, response);
 
-            if (hasUpdate && isBDXProtocolSupported) {
-                auto fabricIndex = handler->GetSubjectDescriptor().fabricIndex;
-                auto nodeId = handler->GetSubjectDescriptor().subject;
-                CHIP_ERROR err = gOtaSender.PrepareForTransfer(fabricIndex, nodeId);
-                if (CHIP_NO_ERROR != err) {
-                    LogErrorOnFailure(err);
-                    handler->AddStatus(cachedCommandPath, Protocols::InteractionModel::Status::Failure);
-                    handle.Release();
-                    return;
-                }
+                      auto hasUpdate = [data.status isEqual:@(MTROtaSoftwareUpdateProviderOTAQueryStatusUpdateAvailable)];
+                      auto isBDXProtocolSupported = [commandParams.protocolsSupported
+                          containsObject:@(MTROtaSoftwareUpdateProviderOTADownloadProtocolBDXSynchronous)];
 
-                auto targetNodeId = handler->GetExchangeContext()->GetSessionHandle()->AsSecureSession()->GetLocalScopedNodeId();
+                      if (hasUpdate && isBDXProtocolSupported) {
+                          auto fabricIndex = handler->GetSubjectDescriptor().fabricIndex;
+                          auto nodeId = handler->GetSubjectDescriptor().subject;
+                          CHIP_ERROR err = gOtaSender.PrepareForTransfer(fabricIndex, nodeId);
+                          if (CHIP_NO_ERROR != err) {
+                              LogErrorOnFailure(err);
+                              handler->AddStatus(cachedCommandPath, Protocols::InteractionModel::Status::Failure);
+                              handle.Release();
+                              return;
+                          }
 
-                char uriBuffer[kMaxBDXURILen];
-                MutableCharSpan uri(uriBuffer);
-                err = bdx::MakeURI(targetNodeId.GetNodeId(), AsCharSpan(data.imageURI), uri);
-                if (CHIP_NO_ERROR != err) {
-                    LogErrorOnFailure(err);
-                    handler->AddStatus(cachedCommandPath, Protocols::InteractionModel::Status::Failure);
-                    handle.Release();
-                    return;
-                }
+                          auto targetNodeId
+                              = handler->GetExchangeContext()->GetSessionHandle()->AsSecureSession()->GetLocalScopedNodeId();
 
-                response.imageURI.SetValue(uri);
-                handler->AddResponse(cachedCommandPath, response);
-                handle.Release();
-                return;
-            }
+                          char uriBuffer[kMaxBDXURILen];
+                          MutableCharSpan uri(uriBuffer);
+                          err = bdx::MakeURI(targetNodeId.GetNodeId(), AsCharSpan(data.imageURI), uri);
+                          if (CHIP_NO_ERROR != err) {
+                              LogErrorOnFailure(err);
+                              handler->AddStatus(cachedCommandPath, Protocols::InteractionModel::Status::Failure);
+                              handle.Release();
+                              return;
+                          }
 
-            handler->AddResponse(cachedCommandPath, response);
-            handle.Release();
-        });
-    };
+                          response.imageURI.SetValue(uri);
+                          handler->AddResponse(cachedCommandPath, response);
+                          handle.Release();
+                          return;
+                      }
+
+                      handler->AddResponse(cachedCommandPath, response);
+                      handle.Release();
+                  }
+
+                                errorHandler:^(NSError *) {
+                                    // Not much we can do here
+                                }];
+          };
 
     auto strongDelegate = mDelegate;
-    dispatch_async(mWorkQueue, ^{
+    dispatch_async(mDelegateNotificationQueue, ^{
         if ([strongDelegate respondsToSelector:@selector(handleQueryImageForNodeID:controller:params:completion:)]) {
             [strongDelegate handleQueryImageForNodeID:@(nodeId)
                                            controller:controller
@@ -547,6 +630,8 @@
 void MTROTAProviderDelegateBridge::HandleApplyUpdateRequest(CommandHandler * commandObj, const ConcreteCommandPath & commandPath,
     const Commands::ApplyUpdateRequest::DecodableType & commandData)
 {
+    assertChipStackLockedByCurrentThread();
+
     NodeId nodeId;
     MTRDeviceController * controller;
     if (!GetPeerNodeInfo(commandObj, commandPath, &nodeId, &controller)) {
@@ -559,25 +644,31 @@
 
     auto completionHandler
         = ^(MTROTASoftwareUpdateProviderClusterApplyUpdateResponseParams * _Nullable data, NSError * _Nullable error) {
-              dispatch_async(mWorkQueue, ^{
-                  CommandHandler * handler = EnsureValidState(handle, cachedCommandPath, "ApplyUpdateRequest", data, error);
-                  VerifyOrReturn(handler != nullptr);
+              [controller
+                  asyncDispatchToMatterQueue:^(chip::Controller::DeviceCommissioner *) {
+                      assertChipStackLockedByCurrentThread();
 
-                  ChipLogDetail(Controller, "ApplyUpdateRequest: application responded with: %s",
-                      [[data description] cStringUsingEncoding:NSUTF8StringEncoding]);
+                      CommandHandler * handler = EnsureValidState(handle, cachedCommandPath, "ApplyUpdateRequest", data, error);
+                      VerifyOrReturn(handler != nullptr);
 
-                  Commands::ApplyUpdateResponse::Type response;
-                  ConvertFromApplyUpdateRequestResponseParms(data, response);
-                  handler->AddResponse(cachedCommandPath, response);
-                  handle.Release();
-              });
+                      ChipLogDetail(Controller, "ApplyUpdateRequest: application responded with: %s",
+                          [[data description] cStringUsingEncoding:NSUTF8StringEncoding]);
+
+                      Commands::ApplyUpdateResponse::Type response;
+                      ConvertFromApplyUpdateRequestResponseParms(data, response);
+                      handler->AddResponse(cachedCommandPath, response);
+                      handle.Release();
+                  }
+                                errorHandler:^(NSError *) {
+                                    // Not much we can do here
+                                }];
           };
 
     auto * commandParams = [[MTROTASoftwareUpdateProviderClusterApplyUpdateRequestParams alloc] init];
     ConvertToApplyUpdateRequestParams(commandData, commandParams);
 
     auto strongDelegate = mDelegate;
-    dispatch_async(mWorkQueue, ^{
+    dispatch_async(mDelegateNotificationQueue, ^{
         if ([strongDelegate respondsToSelector:@selector(handleApplyUpdateRequestForNodeID:controller:params:completion:)]) {
             [strongDelegate handleApplyUpdateRequestForNodeID:@(nodeId)
                                                    controller:controller
@@ -601,6 +692,8 @@
 void MTROTAProviderDelegateBridge::HandleNotifyUpdateApplied(CommandHandler * commandObj, const ConcreteCommandPath & commandPath,
     const Commands::NotifyUpdateApplied::DecodableType & commandData)
 {
+    assertChipStackLockedByCurrentThread();
+
     NodeId nodeId;
     MTRDeviceController * controller;
     if (!GetPeerNodeInfo(commandObj, commandPath, &nodeId, &controller)) {
@@ -612,20 +705,26 @@
     __block ConcreteCommandPath cachedCommandPath(commandPath.mEndpointId, commandPath.mClusterId, commandPath.mCommandId);
 
     auto completionHandler = ^(NSError * _Nullable error) {
-        dispatch_async(mWorkQueue, ^{
-            CommandHandler * handler = EnsureValidState(handle, cachedCommandPath, "NotifyUpdateApplied", error);
-            VerifyOrReturn(handler != nullptr);
+        [controller
+            asyncDispatchToMatterQueue:^(chip::Controller::DeviceCommissioner *) {
+                assertChipStackLockedByCurrentThread();
 
-            handler->AddStatus(cachedCommandPath, Protocols::InteractionModel::Status::Success);
-            handle.Release();
-        });
+                CommandHandler * handler = EnsureValidState(handle, cachedCommandPath, "NotifyUpdateApplied", error);
+                VerifyOrReturn(handler != nullptr);
+
+                handler->AddStatus(cachedCommandPath, Protocols::InteractionModel::Status::Success);
+                handle.Release();
+            }
+                          errorHandler:^(NSError *) {
+                              // Not much we can do here
+                          }];
     };
 
     auto * commandParams = [[MTROTASoftwareUpdateProviderClusterNotifyUpdateAppliedParams alloc] init];
     ConvertToNotifyUpdateAppliedParams(commandData, commandParams);
 
     auto strongDelegate = mDelegate;
-    dispatch_async(mWorkQueue, ^{
+    dispatch_async(mDelegateNotificationQueue, ^{
         if ([strongDelegate respondsToSelector:@selector(handleNotifyUpdateAppliedForNodeID:controller:params:completion:)]) {
             [strongDelegate handleNotifyUpdateAppliedForNodeID:@(nodeId)
                                                     controller:controller