Automation for new TC-CGEN-2.4 (#24560)

* Python OpenWindow plumbing, fix RevokeCommissioning

Fixes two issues:
1) Revoke Commissioning leaves the failsafe timer armed which means
   you can't revoke then re-open the commissioning window
2) The python layer returned prematurely on window open and didn't
   plumb through the returne SetupPayload, so the enhanced window
   wasn't useable

Also adds a test for these two things.

* Fix return type

* automation for  CGEN-2.4

* Try again for cirque

* Restyled by autopep8

* Restyled by isort

Co-authored-by: Restyled.io <commits@restyled.io>
diff --git a/scripts/tests/cirque_tests.sh b/scripts/tests/cirque_tests.sh
index caa7014..b018117 100755
--- a/scripts/tests/cirque_tests.sh
+++ b/scripts/tests/cirque_tests.sh
@@ -44,6 +44,7 @@
     "SplitCommissioningTest"
     "CommissioningFailureTest"
     "CommissioningFailureOnReportTest"
+    "CommissioningWindowTest"
 )
 
 BOLD_GREEN_TEXT="\033[1;32m"
diff --git a/src/app/clusters/administrator-commissioning-server/administrator-commissioning-server.cpp b/src/app/clusters/administrator-commissioning-server/administrator-commissioning-server.cpp
index a78ccaf..b38cc27 100644
--- a/src/app/clusters/administrator-commissioning-server/administrator-commissioning-server.cpp
+++ b/src/app/clusters/administrator-commissioning-server/administrator-commissioning-server.cpp
@@ -193,6 +193,8 @@
 {
     ChipLogProgress(Zcl, "Received command to close commissioning window");
 
+    Server::GetInstance().GetFailSafeContext().ForceFailSafeTimerExpiry();
+
     if (!Server::GetInstance().GetCommissioningWindowManager().IsCommissioningWindowOpen())
     {
         ChipLogError(Zcl, "Commissioning window is currently not open");
diff --git a/src/controller/AutoCommissioner.h b/src/controller/AutoCommissioner.h
index f2807a4..297946f 100644
--- a/src/controller/AutoCommissioner.h
+++ b/src/controller/AutoCommissioner.h
@@ -48,6 +48,12 @@
     CommissioningStage GetNextCommissioningStage(CommissioningStage currentStage, CHIP_ERROR & lastErr);
     DeviceCommissioner * GetCommissioner() { return mCommissioner; }
     CHIP_ERROR PerformStep(CommissioningStage nextStage);
+    CommissioneeDeviceProxy * GetCommissioneeDeviceProxy() { return mCommissioneeDeviceProxy; }
+    /**
+     * The device argument to GetCommandTimeout is the device whose session will
+     * be used for sending the relevant command.
+     */
+    Optional<System::Clock::Timeout> GetCommandTimeout(DeviceProxy * device, CommissioningStage stage) const;
 
 private:
     DeviceProxy * GetDeviceProxyForStep(CommissioningStage nextStage);
@@ -64,11 +70,6 @@
     ByteSpan GetPAI() const { return ByteSpan(mPAI, mPAILen); }
 
     CHIP_ERROR NOCChainGenerated(ByteSpan noc, ByteSpan icac, ByteSpan rcac, IdentityProtectionKeySpan ipk, NodeId adminSubject);
-    /**
-     * The device argument to GetCommandTimeout is the device whose session will
-     * be used for sending the relevant command.
-     */
-    Optional<System::Clock::Timeout> GetCommandTimeout(DeviceProxy * device, CommissioningStage stage) const;
     EndpointId GetEndpoint(const CommissioningStage & stage) const;
     CommissioningStage GetNextCommissioningStageInternal(CommissioningStage currentStage, CHIP_ERROR & lastErr);
 
diff --git a/src/controller/python/ChipDeviceController-ScriptBinding.cpp b/src/controller/python/ChipDeviceController-ScriptBinding.cpp
index ea376ba..62fed10 100644
--- a/src/controller/python/ChipDeviceController-ScriptBinding.cpp
+++ b/src/controller/python/ChipDeviceController-ScriptBinding.cpp
@@ -174,6 +174,8 @@
 PyChipError pychip_ScriptDevicePairingDelegate_SetCommissioningStatusUpdateCallback(
     chip::Controller::DeviceCommissioner * devCtrl,
     chip::Controller::DevicePairingDelegate_OnCommissioningStatusUpdateFunct callback);
+PyChipError pychip_ScriptDevicePairingDelegate_SetOpenWindowCompleteCallback(
+    chip::Controller::DeviceCommissioner * devCtrl, chip::Controller::DevicePairingDelegate_OnWindowOpenCompleteFunct callback);
 
 // BLE
 PyChipError pychip_DeviceCommissioner_CloseBleConnection(chip::Controller::DeviceCommissioner * devCtrl);
@@ -524,6 +526,13 @@
     return ToPyChipError(devCtrl->DiscoverCommissionableNodes(filter));
 }
 
+PyChipError pychip_ScriptDevicePairingDelegate_SetOpenWindowCompleteCallback(
+    chip::Controller::DeviceCommissioner * devCtrl, chip::Controller::DevicePairingDelegate_OnWindowOpenCompleteFunct callback)
+{
+    sPairingDelegate.SetCommissioningWindowOpenCallback(callback);
+    return ToPyChipError(CHIP_NO_ERROR);
+}
+
 PyChipError pychip_DeviceController_OpenCommissioningWindow(chip::Controller::DeviceCommissioner * devCtrl, chip::NodeId nodeid,
                                                             uint16_t timeout, uint32_t iteration, uint16_t discriminator,
                                                             uint8_t optionInt)
@@ -538,8 +547,12 @@
     if (option == Controller::CommissioningWindowOpener::CommissioningWindowOption::kTokenWithRandomPIN)
     {
         SetupPayload payload;
-        return ToPyChipError(Controller::AutoCommissioningWindowOpener::OpenCommissioningWindow(
-            devCtrl, nodeid, System::Clock::Seconds16(timeout), iteration, discriminator, NullOptional, NullOptional, payload));
+        auto opener =
+            Platform::New<Controller::CommissioningWindowOpener>(static_cast<chip::Controller::DeviceController *>(devCtrl));
+        PyChipError err = ToPyChipError(opener->OpenCommissioningWindow(nodeid, System::Clock::Seconds16(timeout), iteration,
+                                                                        discriminator, NullOptional, NullOptional,
+                                                                        sPairingDelegate.GetOpenWindowCallback(opener), payload));
+        return err;
     }
 
     return ToPyChipError(CHIP_ERROR_INVALID_ARGUMENT);
diff --git a/src/controller/python/ChipDeviceController-ScriptDevicePairingDelegate.cpp b/src/controller/python/ChipDeviceController-ScriptDevicePairingDelegate.cpp
index 67cc6ab..eff99f4 100644
--- a/src/controller/python/ChipDeviceController-ScriptDevicePairingDelegate.cpp
+++ b/src/controller/python/ChipDeviceController-ScriptDevicePairingDelegate.cpp
@@ -20,10 +20,21 @@
 #include "ChipDeviceController-ScriptDevicePairingDelegate.h"
 #include "lib/support/TypeTraits.h"
 #include <controller/python/chip/native/PyChipError.h>
+#include <setup_payload/QRCodeSetupPayloadGenerator.h>
 
 namespace chip {
 namespace Controller {
 
+namespace {
+void OnWindowCompleteStatic(void * context, NodeId deviceId, CHIP_ERROR status, SetupPayload payload)
+{
+    auto self = reinterpret_cast<ScriptDevicePairingDelegate *>(context);
+    self->OnOpenCommissioningWindow(deviceId, status, payload);
+}
+} // namespace
+
+ScriptDevicePairingDelegate::ScriptDevicePairingDelegate() : mOpenWindowCallback(OnWindowCompleteStatic, this) {}
+
 void ScriptDevicePairingDelegate::SetKeyExchangeCallback(DevicePairingDelegate_OnPairingCompleteFunct callback)
 {
     mOnPairingCompleteCallback = callback;
@@ -34,6 +45,11 @@
     mOnCommissioningCompleteCallback = callback;
 }
 
+void ScriptDevicePairingDelegate::SetCommissioningWindowOpenCallback(DevicePairingDelegate_OnWindowOpenCompleteFunct callback)
+{
+    mOnWindowOpenCompleteCallback = callback;
+}
+
 void ScriptDevicePairingDelegate::SetCommissioningSuccessCallback(DevicePairingDelegate_OnCommissioningSuccessFunct callback)
 {
     mOnCommissioningSuccessCallback = callback;
@@ -91,5 +107,28 @@
     }
 }
 
+void ScriptDevicePairingDelegate::OnOpenCommissioningWindow(NodeId deviceId, CHIP_ERROR status, SetupPayload payload)
+{
+    if (mOnWindowOpenCompleteCallback != nullptr)
+    {
+        QRCodeSetupPayloadGenerator generator(payload);
+        std::string code;
+        generator.payloadBase38Representation(code);
+        ChipLogProgress(Zcl, "code = %s", code.c_str());
+        mOnWindowOpenCompleteCallback(deviceId, payload.setUpPINCode, code.c_str(), ToPyChipError(status));
+    }
+    if (mWindowOpener != nullptr)
+    {
+        Platform::Delete(mWindowOpener);
+        mWindowOpener = nullptr;
+    }
+}
+Callback::Callback<Controller::OnOpenCommissioningWindow> *
+ScriptDevicePairingDelegate::GetOpenWindowCallback(Controller::CommissioningWindowOpener * context)
+{
+    mWindowOpener = context;
+    return &mOpenWindowCallback;
+}
+
 } // namespace Controller
 } // namespace chip
diff --git a/src/controller/python/ChipDeviceController-ScriptDevicePairingDelegate.h b/src/controller/python/ChipDeviceController-ScriptDevicePairingDelegate.h
index cc41b30..a66ae6b 100644
--- a/src/controller/python/ChipDeviceController-ScriptDevicePairingDelegate.h
+++ b/src/controller/python/ChipDeviceController-ScriptDevicePairingDelegate.h
@@ -26,6 +26,7 @@
 #pragma once
 
 #include <controller/CHIPDeviceController.h>
+#include <controller/CommissioningWindowOpener.h>
 #include <controller/python/chip/native/PyChipError.h>
 
 namespace chip {
@@ -34,6 +35,8 @@
 extern "C" {
 typedef void (*DevicePairingDelegate_OnPairingCompleteFunct)(PyChipError err);
 typedef void (*DevicePairingDelegate_OnCommissioningCompleteFunct)(NodeId nodeId, PyChipError err);
+typedef void (*DevicePairingDelegate_OnWindowOpenCompleteFunct)(NodeId nodeId, uint32_t setupPinCode, const char * setupCode,
+                                                                PyChipError err);
 
 // Used for testing by OpCredsBinding
 typedef void (*DevicePairingDelegate_OnCommissioningSuccessFunct)(PeerId peerId);
@@ -48,25 +51,33 @@
 class ScriptDevicePairingDelegate final : public Controller::DevicePairingDelegate
 {
 public:
+    ScriptDevicePairingDelegate();
     ~ScriptDevicePairingDelegate() = default;
     void SetKeyExchangeCallback(DevicePairingDelegate_OnPairingCompleteFunct callback);
     void SetCommissioningCompleteCallback(DevicePairingDelegate_OnCommissioningCompleteFunct callback);
     void SetCommissioningStatusUpdateCallback(DevicePairingDelegate_OnCommissioningStatusUpdateFunct callback);
     void SetCommissioningSuccessCallback(DevicePairingDelegate_OnCommissioningSuccessFunct callback);
     void SetCommissioningFailureCallback(DevicePairingDelegate_OnCommissioningFailureFunct callback);
+    void SetCommissioningWindowOpenCallback(DevicePairingDelegate_OnWindowOpenCompleteFunct callback);
     void OnPairingComplete(CHIP_ERROR error) override;
     void OnCommissioningComplete(NodeId nodeId, CHIP_ERROR err) override;
     void OnCommissioningSuccess(PeerId peerId) override;
     void OnCommissioningFailure(PeerId peerId, CHIP_ERROR error, CommissioningStage stageFailed,
                                 Optional<Credentials::AttestationVerificationResult> additionalErrorInfo) override;
     void OnCommissioningStatusUpdate(PeerId peerId, CommissioningStage stageCompleted, CHIP_ERROR error) override;
+    Callback::Callback<Controller::OnOpenCommissioningWindow> *
+    GetOpenWindowCallback(Controller::CommissioningWindowOpener * context);
+    void OnOpenCommissioningWindow(NodeId deviceId, CHIP_ERROR status, SetupPayload payload);
 
 private:
     DevicePairingDelegate_OnPairingCompleteFunct mOnPairingCompleteCallback                     = nullptr;
     DevicePairingDelegate_OnCommissioningCompleteFunct mOnCommissioningCompleteCallback         = nullptr;
+    DevicePairingDelegate_OnWindowOpenCompleteFunct mOnWindowOpenCompleteCallback               = nullptr;
     DevicePairingDelegate_OnCommissioningSuccessFunct mOnCommissioningSuccessCallback           = nullptr;
     DevicePairingDelegate_OnCommissioningFailureFunct mOnCommissioningFailureCallback           = nullptr;
     DevicePairingDelegate_OnCommissioningStatusUpdateFunct mOnCommissioningStatusUpdateCallback = nullptr;
+    Callback::Callback<Controller::OnOpenCommissioningWindow> mOpenWindowCallback;
+    Controller::CommissioningWindowOpener * mWindowOpener = nullptr;
 };
 
 } // namespace Controller
diff --git a/src/controller/python/OpCredsBinding.cpp b/src/controller/python/OpCredsBinding.cpp
index f0d9b1f..3b12f8f 100644
--- a/src/controller/python/OpCredsBinding.cpp
+++ b/src/controller/python/OpCredsBinding.cpp
@@ -133,6 +133,30 @@
             // Pretend we received an error from the device during this stage
             err = CHIP_ERROR_INTERNAL;
         }
+        if (mPrematureCompleteAfter == report.stageCompleted)
+        {
+            auto commissioner = chip::Controller::AutoCommissioner::GetCommissioner();
+            auto proxy        = chip::Controller::AutoCommissioner::GetCommissioneeDeviceProxy();
+            auto stage        = chip::Controller::CommissioningStage::kSendComplete;
+            auto params       = chip::Controller::CommissioningParameters();
+            commissioner->PerformCommissioningStep(proxy, stage, params, this, 0, GetCommandTimeout(proxy, stage));
+            return CHIP_NO_ERROR;
+        }
+
+        if (mPrematureCompleteAfter != chip::Controller::CommissioningStage::kError &&
+            report.stageCompleted == chip::Controller::CommissioningStage::kSendComplete)
+        {
+            if (report.Is<chip::Controller::CommissioningErrorInfo>())
+            {
+                uint8_t code     = chip::to_underlying(report.Get<chip::Controller::CommissioningErrorInfo>().commissioningError);
+                mCompletionError = chip::ChipError(chip::ChipError::SdkPart::kIMClusterStatus, code);
+            }
+            else
+            {
+                mCompletionError = err;
+            }
+        }
+
         return chip::Controller::AutoCommissioner::CommissioningStepFinished(err, report);
     }
     // This will cause the COMMISSIONER to fail after the given stage. Setting this to kSecurePairing will cause the
@@ -156,6 +180,15 @@
         mFailOnReportAfterStage = stage;
         return true;
     }
+    bool PrematureCompleteAfter(chip::Controller::CommissioningStage stage)
+    {
+        if (!ValidStage(stage) && stage != chip::Controller::CommissioningStage::kError)
+        {
+            return false;
+        }
+        mPrematureCompleteAfter = stage;
+        return true;
+    }
     bool CheckCallbacks()
     {
         bool successFailureOk;
@@ -208,6 +241,7 @@
         }
         mSimulateFailureOnStage = chip::Controller::CommissioningStage::kError;
         mFailOnReportAfterStage = chip::Controller::CommissioningStage::kError;
+        mPrematureCompleteAfter = chip::Controller::CommissioningStage::kError;
     }
     bool GetTestCommissionerUsed() { return mTestCommissionerUsed; }
     void OnCommissioningSuccess(chip::PeerId peerId) { mReceivedCommissioningSuccess = true; }
@@ -226,19 +260,35 @@
         {
             mReceivedStageFailure[chip::to_underlying(stageCompleted)] = true;
         }
+        if (stageCompleted == chip::Controller::CommissioningStage::kCleanup &&
+            mPrematureCompleteAfter != chip::Controller::CommissioningStage::kError)
+        {
+            // We need to manually clean up the proxy here because we're doing bad things in the name of testing
+            ChipLogProgress(Controller, "Cleaning up dangling proxies");
+            auto commissioner = chip::Controller::AutoCommissioner::GetCommissioner();
+            auto proxy        = chip::Controller::AutoCommissioner::GetCommissioneeDeviceProxy();
+            if (proxy != nullptr)
+            {
+                commissioner->StopPairing(proxy->GetDeviceId());
+            }
+        }
     }
 
+    CHIP_ERROR GetCompletionError() { return mCompletionError; }
+
 private:
     static constexpr uint8_t kNumCommissioningStages = chip::to_underlying(chip::Controller::CommissioningStage::kCleanup) + 1;
     chip::Controller::CommissioningStage mSimulateFailureOnStage            = chip::Controller::CommissioningStage::kError;
     chip::Controller::CommissioningStage mFailOnReportAfterStage            = chip::Controller::CommissioningStage::kError;
+    chip::Controller::CommissioningStage mPrematureCompleteAfter            = chip::Controller::CommissioningStage::kError;
     bool mTestCommissionerUsed                                              = false;
     bool mReceivedCommissioningSuccess                                      = false;
     chip::Controller::CommissioningStage mReceivedCommissioningFailureStage = chip::Controller::CommissioningStage::kError;
     bool mReceivedStageSuccess[kNumCommissioningStages];
     bool mReceivedStageFailure[kNumCommissioningStages];
-    bool mIsWifi   = false;
-    bool mIsThread = false;
+    bool mIsWifi                = false;
+    bool mIsThread              = false;
+    CHIP_ERROR mCompletionError = CHIP_NO_ERROR;
     bool ValidStage(chip::Controller::CommissioningStage stage)
     {
         if (!mIsWifi &&
@@ -469,5 +519,14 @@
 {
     return sTestCommissioner.SimulateFailOnReport(static_cast<chip::Controller::CommissioningStage>(failStage));
 }
+bool pychip_SetTestCommissionerPrematureCompleteAfter(uint8_t stage)
+{
+    return sTestCommissioner.PrematureCompleteAfter(static_cast<chip::Controller::CommissioningStage>(stage));
+}
+
+PyChipError pychip_GetCompletionError()
+{
+    return ToPyChipError(sTestCommissioner.GetCompletionError());
+}
 
 } // extern "C"
diff --git a/src/controller/python/chip/ChipDeviceCtrl.py b/src/controller/python/chip/ChipDeviceCtrl.py
index 8668a09..c608d59 100644
--- a/src/controller/python/chip/ChipDeviceCtrl.py
+++ b/src/controller/python/chip/ChipDeviceCtrl.py
@@ -61,6 +61,8 @@
 _DevicePairingDelegate_OnPairingCompleteFunct = CFUNCTYPE(None, PyChipError)
 _DevicePairingDelegate_OnCommissioningCompleteFunct = CFUNCTYPE(
     None, c_uint64, PyChipError)
+_DevicePairingDelegate_OnOpenWindowCompleteFunct = CFUNCTYPE(
+    None, c_uint64, c_uint32, c_char_p, PyChipError)
 _DevicePairingDelegate_OnCommissioningStatusUpdateFunct = CFUNCTYPE(
     None, c_uint64, c_uint8, PyChipError)
 # void (*)(Device *, CHIP_ERROR).
@@ -256,11 +258,23 @@
             self.state = DCState.IDLE
             self._ChipStack.callbackRes = err
             self._ChipStack.commissioningEventRes = err
+            if self._dmLib.pychip_TestCommissionerUsed():
+                self._ChipStack.commissioningEventRes = self._dmLib.pychip_GetCompletionError()
             self._ChipStack.commissioningCompleteEvent.set()
             self._ChipStack.completeEvent.set()
 
-        def HandlePASEEstablishmentComplete(err: PyChipError):
+        def HandleOpenWindowComplete(nodeid: int, setupPinCode: int, setupCode: str, err: PyChipError) -> None:
             if err.is_success:
+                print("Open Commissioning Window complete setting nodeid {} pincode to {}".format(nodeid, setupPinCode))
+                self._ChipStack.openCommissioningWindowPincode[nodeid] = (setupPinCode, setupCode)
+            else:
+                print("Failed to open commissioning window: {}".format(err))
+
+            self._ChipStack.callbackRes = err
+            self._ChipStack.completeEvent.set()
+
+        def HandlePASEEstablishmentComplete(err: PyChipError):
+            if not err.is_success:
                 print("Failed to establish secure session to device: {}".format(err))
                 self._ChipStack.callbackRes = err.to_exception()
             else:
@@ -288,6 +302,11 @@
         self._dmLib.pychip_ScriptDevicePairingDelegate_SetCommissioningCompleteCallback(
             self.devCtrl, self.cbHandleCommissioningCompleteFunct)
 
+        self.cbHandleOpenWindowCompleteFunct = _DevicePairingDelegate_OnOpenWindowCompleteFunct(
+            HandleOpenWindowComplete)
+        self._dmLib.pychip_ScriptDevicePairingDelegate_SetOpenWindowCompleteCallback(
+            self.devCtrl, self.cbHandleOpenWindowCompleteFunct)
+
         self.state = DCState.IDLE
         self._isActive = True
 
@@ -469,6 +488,10 @@
         return self._dmLib.pychip_SetTestCommissionerSimulateFailureOnReport(
             stage)
 
+    def SetTestCommissionerPrematureCompleteAfter(self, stage: int):
+        return self._dmLib.pychip_SetTestCommissionerPrematureCompleteAfter(
+            stage)
+
     def CheckTestCommissionerCallbacks(self):
         return self._ChipStack.Call(
             lambda: self._dmLib.pychip_TestCommissioningCallbacks()
@@ -512,8 +535,8 @@
         )
         if not self._ChipStack.commissioningCompleteEvent.isSet():
             # Error 50 is a timeout
-            return False
-        return self._ChipStack.commissioningEventRes == 0
+            return False, -1
+        return self._ChipStack.commissioningEventRes == 0, self._ChipStack.commissioningEventRes
 
     def CommissionWithCode(self, setupPayload: str, nodeid: int):
         self.CheckIsActive()
@@ -737,13 +760,14 @@
                 self.devCtrl)
         ).raise_on_error()
 
-    def OpenCommissioningWindow(self, nodeid, timeout, iteration, discriminator, option):
+    def OpenCommissioningWindow(self, nodeid: int, timeout: int, iteration: int, discriminator: int, option: int) -> (int, str):
         self.CheckIsActive()
-
-        self._ChipStack.Call(
+        self._ChipStack.CallAsync(
             lambda: self._dmLib.pychip_DeviceController_OpenCommissioningWindow(
                 self.devCtrl, nodeid, timeout, iteration, discriminator, option)
         ).raise_on_error()
+        self._ChipStack.callbackRes.raise_on_error()
+        return self._ChipStack.openCommissioningWindowPincode[nodeid]
 
     def GetCompressedFabricId(self):
         self.CheckIsActive()
@@ -1366,9 +1390,13 @@
                 c_void_p, _DevicePairingDelegate_OnCommissioningCompleteFunct]
             self._dmLib.pychip_ScriptDevicePairingDelegate_SetCommissioningCompleteCallback.restype = PyChipError
 
+            self._dmLib.pychip_ScriptDevicePairingDelegate_SetOpenWindowCompleteCallback.argtypes = [
+                c_void_p, _DevicePairingDelegate_OnOpenWindowCompleteFunct]
+            self._dmLib.pychip_ScriptDevicePairingDelegate_SetOpenWindowCompleteCallback.restype = PyChipError
+
             self._dmLib.pychip_ScriptDevicePairingDelegate_SetCommissioningStatusUpdateCallback.argtypes = [
                 c_void_p, _DevicePairingDelegate_OnCommissioningStatusUpdateFunct]
-            self._dmLib.pychip_ScriptDevicePairingDelegate_SetCommissioningCompleteCallback.restype = PyChipError
+            self._dmLib.pychip_ScriptDevicePairingDelegate_SetCommissioningStatusUpdateCallback.restype = PyChipError
 
             self._dmLib.pychip_GetConnectedDeviceByNodeId.argtypes = [
                 c_void_p, c_uint64, _DeviceAvailableFunct]
@@ -1415,6 +1443,13 @@
                 c_uint8]
             self._dmLib.pychip_SetTestCommissionerSimulateFailureOnReport.restype = c_bool
 
+            self._dmLib.pychip_SetTestCommissionerPrematureCompleteAfter.argtypes = [
+                c_uint8]
+            self._dmLib.pychip_SetTestCommissionerPrematureCompleteAfter.restype = c_bool
+
+            self._dmLib.pychip_GetCompletionError.argtypes = []
+            self._dmLib.pychip_GetCompletionError.restype = PyChipError
+
             self._dmLib.pychip_DeviceController_IssueNOCChain.argtypes = [
                 c_void_p, py_object, c_char_p, c_size_t, c_uint64
             ]
diff --git a/src/controller/python/chip/ChipStack.py b/src/controller/python/chip/ChipStack.py
index 4154a92..ad21724 100644
--- a/src/controller/python/chip/ChipStack.py
+++ b/src/controller/python/chip/ChipStack.py
@@ -185,6 +185,7 @@
         self.devMgr = None
         self.callbackRes = None
         self.commissioningEventRes = None
+        self.openCommissioningWindowPincode = {}
         self._activeLogFunct = None
         self.addModulePrefixToLogMessage = True
         self._enableServerInteractions = enableServerInteractions
diff --git a/src/controller/python/test/test_scripts/base.py b/src/controller/python/test/test_scripts/base.py
index cca12fd..3188e31 100644
--- a/src/controller/python/test/test_scripts/base.py
+++ b/src/controller/python/test/test_scripts/base.py
@@ -231,10 +231,37 @@
         self.logger.info(f"Found device {res[0]}")
         return res[0]
 
-    def TestPaseOnly(self, ip: str, setuppin: int, nodeid: int):
+    def CreateNewFabricController(self):
+        self.logger.info("Creating 2nd Fabric Admin")
+        self.fabricAdmin2 = self.certificateAuthority.NewFabricAdmin(vendorId=0xFFF1, fabricId=2)
+
+        self.logger.info("Creating Device Controller on 2nd Fabric")
+        self.devCtrl2 = self.fabricAdmin2.NewController(
+            self.controllerNodeId, self.paaTrustStorePath)
+        return True
+
+    async def TestRevokeCommissioningWindow(self, ip: str, setuppin: int, nodeid: int):
+        await self.devCtrl.SendCommand(nodeid, 0, Clusters.AdministratorCommissioning.Commands.OpenBasicCommissioningWindow(180), timedRequestTimeoutMs=10000)
+        if not self.TestPaseOnly(ip=ip, setuppin=setuppin, nodeid=nodeid, devCtrl=self.devCtrl2):
+            return False
+
+        await self.devCtrl2.SendCommand(nodeid, 0, Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=180, breadcrumb=0))
+
+        await self.devCtrl.SendCommand(nodeid, 0, Clusters.AdministratorCommissioning.Commands.RevokeCommissioning(),  timedRequestTimeoutMs=10000)
+        await self.devCtrl.SendCommand(nodeid, 0, Clusters.AdministratorCommissioning.Commands.OpenBasicCommissioningWindow(180), timedRequestTimeoutMs=10000)
+        await self.devCtrl.SendCommand(nodeid, 0, Clusters.AdministratorCommissioning.Commands.RevokeCommissioning(),  timedRequestTimeoutMs=10000)
+        return True
+
+    def TestEnhancedCommissioningWindow(self, ip: str, nodeid: int):
+        pin, code = self.devCtrl.OpenCommissioningWindow(nodeid=nodeid, timeout=600, iteration=10000, discriminator=3840, option=1)
+        return self.TestPaseOnly(ip=ip, nodeid=nodeid, setuppin=pin, devCtrl=self.devCtrl2)
+
+    def TestPaseOnly(self, ip: str, setuppin: int, nodeid: int, devCtrl=None):
+        if devCtrl is None:
+            devCtrl = self.devCtrl
         self.logger.info(
             "Attempting to establish PASE session with device id: {} addr: {}".format(str(nodeid), ip))
-        if self.devCtrl.EstablishPASESessionIP(
+        if devCtrl.EstablishPASESessionIP(
                 ip, setuppin, nodeid) is not None:
             self.logger.info(
                 "Failed to establish PASE session with device id: {} addr: {}".format(str(nodeid), ip))
diff --git a/src/controller/python/test/test_scripts/commissioning_window_test.py b/src/controller/python/test/test_scripts/commissioning_window_test.py
new file mode 100755
index 0000000..6a113ae
--- /dev/null
+++ b/src/controller/python/test/test_scripts/commissioning_window_test.py
@@ -0,0 +1,124 @@
+#!/usr/bin/env python3
+
+#
+#    Copyright (c) 2022 Project CHIP Authors
+#    All rights reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License");
+#    you may not use this file except in compliance with the License.
+#    You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+#
+
+# Commissioning test.
+
+import asyncio
+import os
+import sys
+from optparse import OptionParser
+
+from base import BaseTestHelper, FailIfNot, TestFail, TestTimeout, logger
+
+# The thread network dataset tlv for testing, splitted into T-L-V.
+
+TEST_THREAD_NETWORK_DATASET_TLV = "0e080000000000010000" + \
+    "000300000c" + \
+    "35060004001fffe0" + \
+    "0208fedcba9876543210" + \
+    "0708fd00000000001234" + \
+    "0510ffeeddccbbaa99887766554433221100" + \
+    "030e54657374696e674e6574776f726b" + \
+    "0102d252" + \
+    "041081cb3b2efa781cc778397497ff520fa50c0302a0ff"
+# Network id, for the thread network, current a const value, will be changed to XPANID of the thread network.
+TEST_THREAD_NETWORK_ID = "fedcba9876543210"
+TEST_DISCRIMINATOR = 3840
+
+ENDPOINT_ID = 0
+LIGHTING_ENDPOINT_ID = 1
+GROUP_ID = 0
+
+
+async def main():
+    optParser = OptionParser()
+    optParser.add_option(
+        "-t",
+        "--timeout",
+        action="store",
+        dest="testTimeout",
+        default=75,
+        type='int',
+        help="The program will return with timeout after specified seconds.",
+        metavar="<timeout-second>",
+    )
+    optParser.add_option(
+        "--address",
+        action="store",
+        dest="deviceAddress",
+        default='',
+        type='str',
+        help="Address of the first device",
+    )
+    optParser.add_option(
+        "-p",
+        "--paa-trust-store-path",
+        action="store",
+        dest="paaTrustStorePath",
+        default='',
+        type='str',
+        help="Path that contains valid and trusted PAA Root Certificates.",
+        metavar="<paa-trust-store-path>"
+    )
+
+    (options, remainingArgs) = optParser.parse_args(sys.argv[1:])
+
+    timeoutTicker = TestTimeout(options.testTimeout)
+    timeoutTicker.start()
+
+    test = BaseTestHelper(
+        nodeid=112233, paaTrustStorePath=options.paaTrustStorePath, testCommissioner=False)
+
+    FailIfNot(test.SetNetworkCommissioningParameters(dataset=TEST_THREAD_NETWORK_DATASET_TLV),
+              "Failed to finish network commissioning")
+
+    logger.info("Commissioning DUT from first commissioner")
+    FailIfNot(test.TestPaseOnly(ip=options.deviceAddress, setuppin=20202021, nodeid=1),
+              "Unable to establish PASE connection to device")
+    FailIfNot(test.TestCommissionOnly(nodeid=1), "Unable to commission device")
+
+    logger.info("Creating controller on a new fabric")
+    FailIfNot(test.CreateNewFabricController(), "Unable to create new controller")
+
+    logger.info("Testing RevokeCommissioning")
+    FailIfNot(await test.TestRevokeCommissioningWindow(ip=options.deviceAddress,
+                                                       setuppin=20202021,
+                                                       nodeid=1),
+              "RevokeCommissioning test failed")
+
+    logger.info("Test Enhanced Commissioning Window")
+    FailIfNot(test.TestEnhancedCommissioningWindow(ip=options.deviceAddress, nodeid=1), "EnhancedCommissioningWindow open failed")
+
+    timeoutTicker.stop()
+
+    logger.info("Test finished")
+
+    # TODO: Python device controller cannot be shutdown clean sometimes and will block on AsyncDNSResolverSockets shutdown.
+    # Call os._exit(0) to force close it.
+    os._exit(0)
+
+
+if __name__ == "__main__":
+    try:
+        loop = asyncio.get_event_loop()
+        loop.run_until_complete(main())
+        loop.close()
+    except Exception as ex:
+        logger.exception(ex)
+        TestFail("Exception occurred when running tests.")
diff --git a/src/python_testing/TC_CGEN_2_4.py b/src/python_testing/TC_CGEN_2_4.py
new file mode 100644
index 0000000..c089615
--- /dev/null
+++ b/src/python_testing/TC_CGEN_2_4.py
@@ -0,0 +1,153 @@
+#
+#    Copyright (c) 2022 Project CHIP Authors
+#    All rights reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License");
+#    you may not use this file except in compliance with the License.
+#    You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+#
+
+import asyncio
+import logging
+import queue
+import time
+from threading import Event
+
+import chip.CertificateAuthority
+import chip.clusters as Clusters
+import chip.FabricAdmin
+from chip import ChipDeviceCtrl
+from chip.clusters.Attribute import SubscriptionTransaction, TypedAttributePath
+from chip.interaction_model import InteractionModelError
+from chip.utils import CommissioningBuildingBlocks
+from matter_testing_support import MatterBaseTest, async_test_body, default_matter_test_main
+from mobly import asserts
+
+
+class TC_CGEN_2_4(MatterBaseTest):
+
+    def OpenCommissioningWindow(self) -> int:
+        try:
+            pin, code = self.th1.OpenCommissioningWindow(
+                nodeid=self.dut_node_id, timeout=600, iteration=10000, discriminator=self.matter_test_config.discriminator, option=1)
+            return pin, code
+
+        except Exception as e:
+            logging.exception('Error running OpenCommissioningWindow %s', e)
+            asserts.assert_true(False, 'Failed to open commissioning window')
+
+    async def CommissionToStageSendCompleteAndCleanup(self, stage: int, expectedErrorPart: chip.native.ErrorSDKPart, expectedErrCode: int):
+
+        logging.info("-----------------Fail on step {}-------------------------".format(stage))
+        pin, code = self.OpenCommissioningWindow()
+        self.th2.ResetTestCommissioner()
+        # This will run the commissioning up to the point where stage x is run and the response is sent before the test commissioner simulates a failure
+        self.th2.SetTestCommissionerPrematureCompleteAfter(stage)
+        success, errcode = self.th2.CommissionOnNetwork(
+            nodeId=self.dut_node_id, setupPinCode=pin, filterType=ChipDeviceCtrl.DiscoveryFilterType.LONG_DISCRIMINATOR, filter=self.matter_test_config.discriminator)
+        logging.info('Commissioning complete done. Successful? {}, errorcode = {}'.format(success, errcode))
+        asserts.assert_false(success, 'Commissioning complete did not error as expected')
+        asserts.assert_true(errcode.sdk_part == expectedErrorPart, 'Unexpected error type returned from CommissioningComplete')
+        asserts.assert_true(errcode.sdk_code == expectedErrCode, 'Unexpected error code returned from CommissioningComplete')
+        revokeCmd = Clusters.AdministratorCommissioning.Commands.RevokeCommissioning()
+        await self.th1.SendCommand(nodeid=self.dut_node_id, endpoint=0, payload=revokeCmd, timedRequestTimeoutMs=6000)
+        # The failsafe cleanup is scheduled after the command completes, so give it a bit of time to do that
+        time.sleep(1)
+
+    @async_test_body
+    async def test_TC_CGEN_2_4(self):
+        self.th1 = self.default_controller
+        th2_certificate_authority = self.certificate_authority_manager.NewCertificateAuthority()
+        th2_fabric_admin = th2_certificate_authority.NewFabricAdmin(vendorId=0xFFF1, fabricId=self.th1.fabricId + 1)
+        self.th2 = th2_fabric_admin.NewController(nodeId=2, useTestCommissioner=True)
+        # Stage 3 = kArmFailsafe, expect General error 0x7e (UNSUPPORTED_ACCESS)
+        await self.CommissionToStageSendCompleteAndCleanup(3, chip.native.ErrorSDKPart.IM_GLOBAL_STATUS, 0x7e)
+        # Stage 4 = kConfigRegulatory, expect General error 0x7e (UNSUPPORTED_ACCESS)
+        await self.CommissionToStageSendCompleteAndCleanup(4, chip.native.ErrorSDKPart.IM_GLOBAL_STATUS, 0x7e)
+        # Stage 5 = kSendPAICertificateRequest, expect General error 0x7e (UNSUPPORTED_ACCESS)
+        await self.CommissionToStageSendCompleteAndCleanup(5, chip.native.ErrorSDKPart.IM_GLOBAL_STATUS, 0x7e)
+        # Stage 6 = kSendDACCertificateRequest, expect General error 0x7e (UNSUPPORTED_ACCESS)
+        await self.CommissionToStageSendCompleteAndCleanup(6, chip.native.ErrorSDKPart.IM_GLOBAL_STATUS, 0x7e)
+        # Stage 7 = kSendAttestationRequest, expect General error 0x7e (UNSUPPORTED_ACCESS)
+        await self.CommissionToStageSendCompleteAndCleanup(7, chip.native.ErrorSDKPart.IM_GLOBAL_STATUS, 0x7e)
+        # Stage 9 = kSendOpCertSigningRequest, expect General error 0x7e (UNSUPPORTED_ACCESS)
+        await self.CommissionToStageSendCompleteAndCleanup(9, chip.native.ErrorSDKPart.IM_GLOBAL_STATUS, 0x7e)
+        # Stage 12 = kSendTrustedRootCert, expect General error 0x7e (UNSUPPORTED_ACCESS)
+        await self.CommissionToStageSendCompleteAndCleanup(12, chip.native.ErrorSDKPart.IM_GLOBAL_STATUS, 0x7e)
+        # Stage 13 = kSendNOC, expect cluster error InvalidAuthentication
+        await self.CommissionToStageSendCompleteAndCleanup(13, chip.native.ErrorSDKPart.IM_CLUSTER_STATUS, 0x02)
+
+        logging.info('Step 15 - TH1 opens a commissioning window')
+        pin, code = self.OpenCommissioningWindow()
+
+        logging.info('Step 16 - TH2 fully commissions the DUT')
+        self.th2.ResetTestCommissioner()
+        success, errcode = self.th2.CommissionOnNetwork(
+            nodeId=self.dut_node_id, setupPinCode=pin, filterType=ChipDeviceCtrl.DiscoveryFilterType.LONG_DISCRIMINATOR, filter=self.matter_test_config.discriminator)
+        logging.info('Commissioning complete done. Successful? {}, errorcode = {}'.format(success, errcode))
+
+        logging.info('Step 17 - TH1 sends an arm failsafe')
+        cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=900, breadcrumb=0)
+        # This will throw if not successful
+        await self.th1.SendCommand(nodeid=self.dut_node_id, endpoint=0, payload=cmd)
+
+        logging.info('Step 18 - TH1 reads the location capability')
+        attr = Clusters.GeneralCommissioning.Attributes.LocationCapability
+        cap = await self.read_single_attribute(dev_ctrl=self.th1, node_id=self.dut_node_id, endpoint=0, attribute=attr)
+        if cap == Clusters.GeneralCommissioning.Enums.RegulatoryLocationType.kIndoor:
+            newloc = Clusters.GeneralCommissioning.Enums.RegulatoryLocationType.kOutdoor
+        elif cap == Clusters.GeneralCommissioning.Enums.RegulatoryLocationType.kOutdoor:
+            newloc = Clusters.GeneralCommissioning.Enums.RegulatoryLocationType.kIndoor
+        else:
+            # TODO: figure out how to use the extender
+            #newloc = MatterIntEnum.extend_enum_if_value_doesnt_exist(Clusters.GeneralCommissioning.Enums.RegulatoryLocationType, 3)
+            newlog = cap
+
+        logging.info('Step 19 Send SetRgulatoryConfig with incorrect location')
+        #cmd = Clusters.GeneralCommissioning.Commands.SetRegulatoryConfig(newRegulatoryConfig=newloc, countryCode="XX", breadcrumb=0)
+        # try:
+        #    await self.th1.SendCommand(nodeid=self.dut_node_id, endpoint=0, payload=cmd)
+        # except InteractionModelError as ex:
+        #    print("got the real error")
+        #    pass
+
+        logging.info('Step 20 - TH2 sends CommissioningComplete')
+        cmd = Clusters.GeneralCommissioning.Commands.CommissioningComplete()
+        resp = await self.th2.SendCommand(nodeid=self.dut_node_id, endpoint=0, payload=cmd)
+        asserts.assert_true(isinstance(resp, Clusters.GeneralCommissioning.Commands.CommissioningCompleteResponse),
+                            'Incorrect response type from command')
+        asserts.assert_true(
+            resp.errorCode == Clusters.GeneralCommissioning.Enums.CommissioningError.kInvalidAuthentication, 'Incorrect error code')
+
+        logging.info('Step 21 - TH1 sends an arm failsafe with timeout==0')
+        cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=0, breadcrumb=0)
+        # This will throw if not successful
+        await self.th1.SendCommand(nodeid=self.dut_node_id, endpoint=0, payload=cmd)
+
+        logging.info('Step 22 - TH1 sends CommissioningComplete')
+        cmd = Clusters.GeneralCommissioning.Commands.CommissioningComplete()
+        resp = await self.th2.SendCommand(nodeid=self.dut_node_id, endpoint=0, payload=cmd)
+        asserts.assert_true(isinstance(resp, Clusters.GeneralCommissioning.Commands.CommissioningCompleteResponse),
+                            'Incorrect response type from command')
+        asserts.assert_true(resp.errorCode == Clusters.GeneralCommissioning.Enums.CommissioningError.kNoFailSafe,
+                            'Incorrect error code')
+
+        logging.info('Step 23 - TH2 reads fabric index')
+        attr = Clusters.OperationalCredentials.Attributes.CurrentFabricIndex
+        th2FabricIndex = await self.read_single_attribute(dev_ctrl=self.th2, node_id=self.dut_node_id, endpoint=0, attribute=attr)
+
+        logging.info('Step 24 - TH1 removes TH2')
+        cmd = Clusters.OperationalCredentials.Commands.RemoveFabric(fabricIndex=th2FabricIndex)
+        await self.th1.SendCommand(nodeid=self.dut_node_id, endpoint=0, payload=cmd)
+
+
+if __name__ == "__main__":
+    default_matter_test_main()
diff --git a/src/test_driver/linux-cirque/CommissioningWindowTest.py b/src/test_driver/linux-cirque/CommissioningWindowTest.py
new file mode 100755
index 0000000..16fad7e
--- /dev/null
+++ b/src/test_driver/linux-cirque/CommissioningWindowTest.py
@@ -0,0 +1,117 @@
+#!/usr/bin/env python3
+"""
+Copyright (c) 2021 Project CHIP Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+"""
+
+import logging
+import os
+import pprint
+import sys
+import time
+
+from helper.CHIPTestBase import CHIPVirtualHome
+
+logger = logging.getLogger('MobileDeviceTest')
+logger.setLevel(logging.INFO)
+
+sh = logging.StreamHandler()
+sh.setFormatter(
+    logging.Formatter(
+        '%(asctime)s [%(name)s] %(levelname)s %(message)s'))
+logger.addHandler(sh)
+
+CHIP_PORT = 5540
+
+CIRQUE_URL = "http://localhost:5000"
+CHIP_REPO = os.path.join(os.path.abspath(
+    os.path.dirname(__file__)), "..", "..", "..")
+TEST_EXTPANID = "fedcba9876543210"
+TEST_DISCRIMINATOR = 3840
+TEST_DISCRIMINATOR2 = 3584
+MATTER_DEVELOPMENT_PAA_ROOT_CERTS = "credentials/development/paa-root-certs"
+
+DEVICE_CONFIG = {
+    'device0': {
+        'type': 'MobileDevice',
+        'base_image': 'connectedhomeip/chip-cirque-device-base',
+        'capability': ['TrafficControl', 'Mount'],
+        'rcp_mode': True,
+        'docker_network': 'Ipv6',
+        'traffic_control': {'latencyMs': 100},
+        "mount_pairs": [[CHIP_REPO, CHIP_REPO]],
+    },
+    'device1': {
+        'type': 'CHIPEndDevice',
+        'base_image': 'connectedhomeip/chip-cirque-device-base',
+        'capability': ['Thread', 'TrafficControl', 'Mount'],
+        'rcp_mode': True,
+        'docker_network': 'Ipv6',
+        'traffic_control': {'latencyMs': 100},
+        "mount_pairs": [[CHIP_REPO, CHIP_REPO]],
+    },
+}
+
+
+class TestCommissioningWindow(CHIPVirtualHome):
+    def __init__(self, device_config):
+        super().__init__(CIRQUE_URL, device_config)
+        self.logger = logger
+
+    def setup(self):
+        self.initialize_home()
+
+    def test_routine(self):
+        self.run_controller_test()
+
+    def run_controller_test(self):
+        servers = [{
+            "ip": device['description']['ipv6_addr'],
+            "id": device['id']
+        } for device in self.non_ap_devices
+            if device['type'] == 'CHIPEndDevice']
+        req_ids = [device['id'] for device in self.non_ap_devices
+                   if device['type'] == 'MobileDevice']
+
+        servers[0]['discriminator'] = TEST_DISCRIMINATOR
+        servers[0]['nodeid'] = 1
+
+        for server in servers:
+            self.execute_device_cmd(server['id'], "CHIPCirqueDaemon.py -- run gdb -return-child-result -q -ex \"set pagination off\" -ex run -ex \"bt 25\" --args {} --thread --discriminator {}".format(
+                os.path.join(CHIP_REPO, "out/debug/standalone/chip-all-clusters-app"), server['discriminator']))
+
+        self.reset_thread_devices([server['id'] for server in servers])
+
+        req_device_id = req_ids[0]
+
+        self.execute_device_cmd(req_device_id, "pip3 install {}".format(os.path.join(
+            CHIP_REPO, "out/debug/linux_x64_gcc/controller/python/chip_clusters-0.0-py3-none-any.whl")))
+        self.execute_device_cmd(req_device_id, "pip3 install {}".format(os.path.join(
+            CHIP_REPO, "out/debug/linux_x64_gcc/controller/python/chip_core-0.0-cp37-abi3-linux_x86_64.whl")))
+        self.execute_device_cmd(req_device_id, "pip3 install {}".format(os.path.join(
+            CHIP_REPO, "out/debug/linux_x64_gcc/controller/python/chip_repl-0.0-py3-none-any.whl")))
+
+        command = "gdb -return-child-result -q -ex run -ex bt --args python3 {} -t 150 --address {} --paa-trust-store-path {}".format(
+            os.path.join(
+                CHIP_REPO, "src/controller/python/test/test_scripts/commissioning_window_test.py"),
+            servers[0]['ip'],
+            os.path.join(CHIP_REPO, MATTER_DEVELOPMENT_PAA_ROOT_CERTS))
+        ret = self.execute_device_cmd(req_device_id, command)
+
+        self.assertEqual(ret['return_code'], '0',
+                         "Test failed: non-zero return code")
+
+
+if __name__ == "__main__":
+    sys.exit(TestCommissioningWindow(DEVICE_CONFIG).run_test())