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())