[Matter.framework] Invalidate the CASE session if something calls TriggerResubscriptionWithReason and it has not been established yet (#36298)

diff --git a/src/app/CASESessionManager.cpp b/src/app/CASESessionManager.cpp
index 766e601..4222421 100644
--- a/src/app/CASESessionManager.cpp
+++ b/src/app/CASESessionManager.cpp
@@ -188,6 +188,12 @@
         peerId, MakeOptional(Transport::SecureSession::Type::kCASE), transportPayloadCapability);
 }
 
+void CASESessionManager::ReleaseSession(const ScopedNodeId & peerId)
+{
+    auto * session = mConfig.sessionSetupPool->FindSessionSetup(peerId, false);
+    ReleaseSession(session);
+}
+
 void CASESessionManager::ReleaseSession(OperationalSessionSetup * session)
 {
     if (session != nullptr)
diff --git a/src/app/CASESessionManager.h b/src/app/CASESessionManager.h
index e536a62..c6d0a8c 100644
--- a/src/app/CASESessionManager.h
+++ b/src/app/CASESessionManager.h
@@ -142,6 +142,7 @@
 #endif // CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
                                 TransportPayloadCapability transportPayloadCapability = TransportPayloadCapability::kMRPPayload);
 
+    void ReleaseSession(const ScopedNodeId & peerId);
     void ReleaseSessionsForFabric(FabricIndex fabricIndex);
 
     void ReleaseAllSessions();
diff --git a/src/controller/CHIPDeviceController.h b/src/controller/CHIPDeviceController.h
index 2ec0203..0536856 100644
--- a/src/controller/CHIPDeviceController.h
+++ b/src/controller/CHIPDeviceController.h
@@ -204,6 +204,16 @@
         return nullptr;
     }
 
+    CASESessionManager * CASESessionMgr()
+    {
+        if (mSystemState)
+        {
+            return mSystemState->CASESessionMgr();
+        }
+
+        return nullptr;
+    }
+
     Messaging::ExchangeManager * ExchangeMgr()
     {
         if (mSystemState != nullptr)
diff --git a/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.h b/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.h
index 0cfb25f..f9f1e9d 100644
--- a/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.h
+++ b/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.h
@@ -179,6 +179,12 @@
 - (void)invalidateCASESessionForNode:(NSNumber *)nodeID;
 
 /**
+ * Invalidate the CASE session establishment for the specified node ID.
+ * Must not be called on the Matter event queue.
+ */
+- (void)invalidateCASESessionEstablishmentForNode:(NSNumber *)nodeID;
+
+/**
  * Download log of the desired type from the device.
  */
 - (void)downloadLogFromNodeWithID:(NSNumber *)nodeID
diff --git a/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.mm b/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.mm
index 1a164e5..74e3437 100644
--- a/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.mm
+++ b/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.mm
@@ -1619,6 +1619,17 @@
     [self syncRunOnWorkQueue:block error:nil];
 }
 
+- (void)invalidateCASESessionEstablishmentForNode:(NSNumber *)nodeID;
+{
+    auto block = ^{
+        auto caseSessionMgr = self->_cppCommissioner->CASESessionMgr();
+        VerifyOrDie(caseSessionMgr != nullptr);
+        caseSessionMgr->ReleaseSession(self->_cppCommissioner->GetPeerScopedId(nodeID.unsignedLongLongValue));
+    };
+
+    [self syncRunOnWorkQueue:block error:nil];
+}
+
 - (void)operationalInstanceAdded:(NSNumber *)nodeID
 {
     // Don't use deviceForNodeID here, because we don't want to create the
diff --git a/src/darwin/Framework/CHIP/MTRDevice_Concrete.mm b/src/darwin/Framework/CHIP/MTRDevice_Concrete.mm
index a7b5d25..5668f61 100644
--- a/src/darwin/Framework/CHIP/MTRDevice_Concrete.mm
+++ b/src/darwin/Framework/CHIP/MTRDevice_Concrete.mm
@@ -898,6 +898,12 @@
             subscriptionCallback->ResetResubscriptionBackoff();
         }
         readClientToResubscribe->TriggerResubscribeIfScheduled(reason.UTF8String);
+    } else if (_internalDeviceState == MTRInternalDeviceStateSubscribing && nodeLikelyReachable) {
+        // If we have reason to suspect that the node is now reachable and we haven’t established a
+        // CASE session yet, let’s consider it to be stalled and invalidate the pairing session.
+        dispatch_async(self.queue, ^{
+            [[self _concreteController] invalidateCASESessionEstablishmentForNode:self->_nodeID];
+        });
     }
 }