Remove session accessors on non-concrete MTRDeviceController. (#36317)

CASE/PASE sessions only make sense for concrete controllers.
diff --git a/src/darwin/Framework/CHIP/MTRBaseDevice.mm b/src/darwin/Framework/CHIP/MTRBaseDevice.mm
index 3868060..0f86a76 100644
--- a/src/darwin/Framework/CHIP/MTRBaseDevice.mm
+++ b/src/darwin/Framework/CHIP/MTRBaseDevice.mm
@@ -233,11 +233,6 @@
 
 @end
 
-@interface MTRBaseDevice ()
-// Will return nil if our controller is not in fact a concrete controller.
-@property (nullable, nonatomic, strong, readonly) MTRDeviceController_Concrete * concreteController;
-@end
-
 @implementation MTRBaseDevice
 
 - (instancetype)initWithPASEDevice:(chip::DeviceProxy *)device controller:(MTRDeviceController *)controller
diff --git a/src/darwin/Framework/CHIP/MTRBaseDevice_Internal.h b/src/darwin/Framework/CHIP/MTRBaseDevice_Internal.h
index fa76646..4cf02eb 100644
--- a/src/darwin/Framework/CHIP/MTRBaseDevice_Internal.h
+++ b/src/darwin/Framework/CHIP/MTRBaseDevice_Internal.h
@@ -19,6 +19,7 @@
 #import <Foundation/Foundation.h>
 
 #import "MTRDefines_Internal.h"
+#import "MTRDeviceController_Concrete.h"
 
 #include <app/AttributePathParams.h>
 #include <app/ConcreteAttributePath.h>
@@ -210,6 +211,11 @@
                                 queue:(dispatch_queue_t)queue
                            completion:(MTRDeviceResponseHandler)completion;
 
+/**
+ * Will return nil if our controller is not in fact a concrete controller.
+ */
+@property (nullable, nonatomic, strong, readonly) MTRDeviceController_Concrete * concreteController;
+
 @end
 
 @interface MTRClusterPath ()
diff --git a/src/darwin/Framework/CHIP/MTRCallbackBridgeBase.h b/src/darwin/Framework/CHIP/MTRCallbackBridgeBase.h
index d9c9815..e8b0d1e 100644
--- a/src/darwin/Framework/CHIP/MTRCallbackBridgeBase.h
+++ b/src/darwin/Framework/CHIP/MTRCallbackBridgeBase.h
@@ -18,8 +18,10 @@
 #import <Foundation/Foundation.h>
 
 #import "MTRBaseDevice_Internal.h"
+#import "MTRDeviceController_Concrete.h"
 #import "MTRDeviceController_Internal.h"
 #import "MTRError_Internal.h"
+#import "MTRLogging_Internal.h"
 #import "zap-generated/MTRBaseClusters.h"
 
 #include <app/data-model/NullObject.h>
@@ -37,17 +39,6 @@
 class MTRCallbackBridgeBase {
 public:
     /**
-     * Run the given MTRActionBlock on the Matter thread, after getting a CASE
-     * session (possibly pre-existing) to the given node ID on the fabric
-     * represented by the given MTRDeviceController.  On success, convert the
-     * success value to whatever type it needs to be to call the callback type
-     * we're templated over.  Once this function has been called, on a callback
-     * bridge allocated with `new`, the bridge object must not be accessed by
-     * the caller.  The action block will handle deleting the bridge.
-     */
-    void DispatchAction(chip::NodeId nodeID, MTRDeviceController * controller) && { ActionWithNodeID(nodeID, controller); }
-
-    /**
      * Run the given MTRActionBlock on the Matter thread after getting a secure
      * session corresponding to the given MTRBaseDevice.  On success, convert
      * the success value to whatever type it needs to be to call the callback
@@ -60,7 +51,7 @@
         if (device.isPASEDevice) {
             ActionWithPASEDevice(device);
         } else {
-            ActionWithNodeID(device.nodeID, device.deviceController);
+            ActionWithNodeID(device.nodeID, device.concreteController);
         }
     }
 
@@ -81,22 +72,30 @@
     {
         LogRequestStart();
 
-        // TODO: Figure out whether we can usefully get an MTRDeviceController_Concrete in here, so
-        // we can move getSessionForCommissioneeDevice off of MTRDeviceController_Internal.  Ideally
-        // without bloating this inline method too much.
-        [device.deviceController getSessionForCommissioneeDevice:device.nodeID
-                                                      completion:^(chip::Messaging::ExchangeManager * exchangeManager,
-                                                          const chip::Optional<chip::SessionHandle> & session, NSError * _Nullable error, NSNumber * _Nullable retryDelay) {
-                                                          MaybeDoAction(exchangeManager, session, error);
-                                                      }];
+        MTRDeviceController_Concrete * _Nullable controller = device.concreteController;
+        if (!controller) {
+            MTR_LOG_ERROR("Trying to perform action with PASE device with a non-concrete controller");
+            MaybeDoAction(nullptr, chip::NullOptional, [MTRError errorForCHIPErrorCode:CHIP_ERROR_INCORRECT_STATE]);
+            return;
+        }
+
+        [controller getSessionForCommissioneeDevice:device.nodeID
+                                         completion:^(chip::Messaging::ExchangeManager * exchangeManager,
+                                             const chip::Optional<chip::SessionHandle> & session, NSError * _Nullable error, NSNumber * _Nullable retryDelay) {
+                                             MaybeDoAction(exchangeManager, session, error);
+                                         }];
     }
 
-    void ActionWithNodeID(chip::NodeId nodeID, MTRDeviceController * controller)
+    void ActionWithNodeID(chip::NodeId nodeID, MTRDeviceController_Concrete * _Nullable controller)
     {
         LogRequestStart();
 
-        // TODO: Figure out whether we can usefully get an MTRDeviceController_Concrete in here, so
-        // we can move getSessionForNode off of MTRDeviceController_Internal.
+        if (!controller) {
+            MTR_LOG_ERROR("Trying to perform action on node ID 0x%016llX (%llu) with a non-concrete controller", nodeID, nodeID);
+            MaybeDoAction(nullptr, chip::NullOptional, [MTRError errorForCHIPErrorCode:CHIP_ERROR_INCORRECT_STATE]);
+            return;
+        }
+
         [controller getSessionForNode:nodeID
                            completion:^(chip::Messaging::ExchangeManager * _Nullable exchangeManager,
                                const chip::Optional<chip::SessionHandle> & session, NSError * _Nullable error, NSNumber * _Nullable retryDelay) {
diff --git a/src/darwin/Framework/CHIP/MTRDeviceController.mm b/src/darwin/Framework/CHIP/MTRDeviceController.mm
index 8b8295d..5850d88 100644
--- a/src/darwin/Framework/CHIP/MTRDeviceController.mm
+++ b/src/darwin/Framework/CHIP/MTRDeviceController.mm
@@ -535,18 +535,6 @@
     return NO;
 }
 
-- (void)getSessionForNode:(chip::NodeId)nodeID completion:(MTRInternalDeviceConnectionCallback)completion
-{
-    MTR_ABSTRACT_METHOD();
-    completion(nullptr, chip::NullOptional, [MTRError errorForCHIPErrorCode:CHIP_ERROR_INCORRECT_STATE], nil);
-}
-
-- (void)getSessionForCommissioneeDevice:(chip::NodeId)deviceID completion:(MTRInternalDeviceConnectionCallback)completion
-{
-    MTR_ABSTRACT_METHOD();
-    completion(nullptr, chip::NullOptional, [MTRError errorForCHIPErrorCode:CHIP_ERROR_INCORRECT_STATE], nil);
-}
-
 - (void)asyncDispatchToMatterQueue:(dispatch_block_t)block errorHandler:(nullable MTRDeviceErrorHandler)errorHandler
 {
     // TODO: Figure out how to get callsites to have an MTRDeviceController_Concrete.
diff --git a/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.h b/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.h
index 0cfb25f..80f5b22 100644
--- a/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.h
+++ b/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.h
@@ -121,6 +121,23 @@
 - (void)clearPendingShutdown;
 
 /**
+ * Ensure we have a CASE session to the given node ID and then call the provided
+ * connection callback.  This may be called on any queue (including the Matter
+ * event queue) and on success will always call the provided connection callback
+ * on the Matter queue, asynchronously.  Consumers must be prepared to run on
+ * the Matter queue (an in particular must not use any APIs that will try to do
+ * sync dispatch to the Matter queue).
+ *
+ * If the controller is not running when this function is called, it will
+ * synchronously invoke the completion with an error, on whatever queue
+ * getSessionForNode was called on.
+ *
+ * If the controller is not running when the async dispatch on the Matter queue
+ * happens, the completion will be invoked with an error on the Matter queue.
+ */
+- (void)getSessionForNode:(chip::NodeId)nodeID completion:(MTRInternalDeviceConnectionCallback)completion;
+
+/**
  * Since getSessionForNode now enqueues by the subscription pool for Thread
  * devices, MTRDevice_Concrete needs a direct non-queued access because it already
  * makes use of the subscription pool.
@@ -128,6 +145,23 @@
 - (void)directlyGetSessionForNode:(chip::NodeId)nodeID completion:(MTRInternalDeviceConnectionCallback)completion;
 
 /**
+ * Get a session for the commissionee device with the given device id.  This may
+ * be called on any queue (including the Matter event queue) and on success will
+ * always call the provided connection callback on the Matter queue,
+ * asynchronously.  Consumers must be prepared to run on the Matter queue (an in
+ * particular must not use any APIs that will try to do sync dispatch to the
+ * Matter queue).
+ *
+ * If the controller is not running when this function is called, it will
+ * synchronously invoke the completion with an error, on whatever queue
+ * getSessionForCommissioneeDevice was called on.
+ *
+ * If the controller is not running when the async dispatch on the Matter queue
+ * happens, the completion will be invoked with an error on the Matter queue.
+ */
+- (void)getSessionForCommissioneeDevice:(chip::NodeId)deviceID completion:(MTRInternalDeviceConnectionCallback)completion;
+
+/**
  * Notify the controller that a new operational instance with the given node id
  * and a compressed fabric id that matches this controller has been observed.
  */
diff --git a/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h b/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h
index c26518d..145f329 100644
--- a/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h
+++ b/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h
@@ -122,40 +122,6 @@
 @property (nonatomic, retain, nullable) NSData * rootPublicKey;
 
 /**
- * Ensure we have a CASE session to the given node ID and then call the provided
- * connection callback.  This may be called on any queue (including the Matter
- * event queue) and on success will always call the provided connection callback
- * on the Matter queue, asynchronously.  Consumers must be prepared to run on
- * the Matter queue (an in particular must not use any APIs that will try to do
- * sync dispatch to the Matter queue).
- *
- * If the controller is not running when this function is called, it will
- * synchronously invoke the completion with an error, on whatever queue
- * getSessionForNode was called on.
- *
- * If the controller is not running when the async dispatch on the Matter queue
- * happens, the completion will be invoked with an error on the Matter queue.
- */
-- (void)getSessionForNode:(chip::NodeId)nodeID completion:(MTRInternalDeviceConnectionCallback)completion;
-
-/**
- * Get a session for the commissionee device with the given device id.  This may
- * be called on any queue (including the Matter event queue) and on success will
- * always call the provided connection callback on the Matter queue,
- * asynchronously.  Consumers must be prepared to run on the Matter queue (an in
- * particular must not use any APIs that will try to do sync dispatch to the
- * Matter queue).
- *
- * If the controller is not running when this function is called, it will
- * synchronously invoke the completion with an error, on whatever queue
- * getSessionForCommissioneeDevice was called on.
- *
- * If the controller is not running when the async dispatch on the Matter queue
- * happens, the completion will be invoked with an error on the Matter queue.
- */
-- (void)getSessionForCommissioneeDevice:(chip::NodeId)deviceID completion:(MTRInternalDeviceConnectionCallback)completion;
-
-/**
  * Try to asynchronously dispatch the given block on the Matter queue.  If the
  * controller is not running either at call time or when the block would be
  * about to run, the provided error handler will be called with an error.  Note
diff --git a/src/darwin/Framework/CHIPTests/MTRPerControllerStorageTests.m b/src/darwin/Framework/CHIPTests/MTRPerControllerStorageTests.m
index 0715663..1ead469 100644
--- a/src/darwin/Framework/CHIPTests/MTRPerControllerStorageTests.m
+++ b/src/darwin/Framework/CHIPTests/MTRPerControllerStorageTests.m
@@ -2706,8 +2706,8 @@
 
             // Given the base device read is happening on the 5th device, at the completion
             // time of the first [pool size] subscriptions, the BaseDevice's request to
-            // read can't have completed, as it should be gated on its call to the
-            // MTRDeviceController's getSessionForNode: call.
+            // read can't have completed, as it should be gated on its call to
+            // MTRDeviceController_Concrete's getSessionForNode:.
             if (subscriptionDequeueCount <= (orderedDeviceIDs.count - subscriptionPoolSize)) {
                 XCTAssertFalse(baseDeviceReadCompleted);
             }