Skip to content

Commit 946e6a6

Browse files
tehampsonPeterC1965
authored andcommitted
Update MCORE_FS_1_2 to latest testplan (project-chip#35097)
1 parent f726fad commit 946e6a6

File tree

1 file changed

+163
-90
lines changed

1 file changed

+163
-90
lines changed

src/python_testing/TC_MCORE_FS_1_2.py

+163-90
Original file line numberDiff line numberDiff line change
@@ -19,27 +19,102 @@
1919
# for details about the block below.
2020
#
2121

22-
import base64
22+
import hashlib
2323
import logging
24+
import os
2425
import queue
26+
import secrets
27+
import signal
28+
import struct
29+
import subprocess
2530
import time
31+
import uuid
32+
from dataclasses import dataclass
2633

2734
import chip.clusters as Clusters
2835
from chip import ChipDeviceCtrl
29-
from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main
36+
from ecdsa.curves import NIST256p
37+
from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main, type_matches
3038
from mobly import asserts
3139
from TC_SC_3_6 import AttributeChangeAccumulator
3240

41+
# Length of `w0s` and `w1s` elements
42+
WS_LENGTH = NIST256p.baselen + 8
43+
44+
45+
def _generate_verifier(passcode: int, salt: bytes, iterations: int) -> bytes:
46+
ws = hashlib.pbkdf2_hmac('sha256', struct.pack('<I', passcode), salt, iterations, WS_LENGTH * 2)
47+
w0 = int.from_bytes(ws[:WS_LENGTH], byteorder='big') % NIST256p.order
48+
w1 = int.from_bytes(ws[WS_LENGTH:], byteorder='big') % NIST256p.order
49+
L = NIST256p.generator * w1
50+
51+
return w0.to_bytes(NIST256p.baselen, byteorder='big') + L.to_bytes('uncompressed')
52+
53+
54+
@dataclass
55+
class _SetupParamters:
56+
setup_qr_code: str
57+
manual_code: int
58+
discriminator: int
59+
passcode: int
60+
3361

3462
class TC_MCORE_FS_1_2(MatterBaseTest):
63+
@async_test_body
64+
async def setup_class(self):
65+
super().setup_class()
66+
self._app_th_server_process = None
67+
self._th_server_kvs = None
68+
69+
def teardown_class(self):
70+
if self._app_th_server_process is not None:
71+
logging.warning("Stopping app with SIGTERM")
72+
self._app_th_server_process.send_signal(signal.SIGTERM.value)
73+
self._app_th_server_process.wait()
74+
75+
if self._th_server_kvs is not None:
76+
os.remove(self._th_server_kvs)
77+
super().teardown_class()
78+
79+
async def _create_th_server(self, port):
80+
# These are default testing values
81+
setup_params = _SetupParamters(setup_qr_code="MT:-24J0AFN00KA0648G00",
82+
manual_code=34970112332, discriminator=3840, passcode=20202021)
83+
kvs = f'kvs_{str(uuid.uuid4())}'
84+
85+
cmd = [self._th_server_app_path]
86+
cmd.extend(['--secured-device-port', str(port)])
87+
cmd.extend(['--discriminator', str(setup_params.discriminator)])
88+
cmd.extend(['--passcode', str(setup_params.passcode)])
89+
cmd.extend(['--KVS', kvs])
90+
91+
# TODO: Determine if we want these logs cooked or pushed to somewhere else
92+
logging.info("Starting TH_SERVER")
93+
self._app_th_server_process = subprocess.Popen(cmd)
94+
self._th_server_kvs = kvs
95+
logging.info("Started TH_SERVER")
96+
time.sleep(3)
97+
return setup_params
98+
99+
def _ask_for_vendor_commissioning_ux_operation(self, setup_params: _SetupParamters):
100+
self.wait_for_user_input(
101+
prompt_msg=f"Using the DUT vendor's provided interface, commission the ICD device using the following parameters:\n"
102+
f"- discriminator: {setup_params.discriminator}\n"
103+
f"- setupPinCode: {setup_params.passcode}\n"
104+
f"- setupQRCode: {setup_params.setup_qr_code}\n"
105+
f"- setupManualcode: {setup_params.manual_code}\n"
106+
f"If using FabricSync Admin test app, you may type:\n"
107+
f">>> pairing onnetwork 111 {setup_params.passcode}")
108+
35109
def steps_TC_MCORE_FS_1_2(self) -> list[TestStep]:
36-
steps = [TestStep(1, "TH_FSA subscribes to all the Bridged Device Basic Information clusters provided by DUT_FSA to identify the presence of a Bridged Node endpoint with a UniqueID matching the UniqueID provided by the BasicInformationCluster of the TH_SED_DUT."),
37-
TestStep(2, "TH_FSA initiates commissioning of TH_SED_DUT by sending the OpenCommissioningWindow command to the Administrator Commissioning Cluster on the endpoint with the uniqueID matching that of TH_SED_DUT."),
38-
TestStep(3, "TH_FSA completes commissioning of TH_SED_DUT using the Enhanced Commissioning Method."),
39-
TestStep(4, "Commission TH_SED_L onto DUT_FSA’s fabric using the manufacturer specified mechanism."),
40-
TestStep(5, "TH_FSA waits for subscription report from a the Bridged Device Basic Information clusters provided by DUT_FSA to identify the presence of a Bridged Node endpoint with a UniqueID matching the UniqueID provided by the BasicInformationCluster of the TH_SED_L."),
41-
TestStep(6, "TH_FSA initiates commissions of TH_SED_L by sending the OpenCommissioningWindow command to the Administrator Commissioning Cluster on the endpoint with the uniqueID matching that of TH_SED_L."),
42-
TestStep(7, "TH_FSA completes commissioning of TH_SED_L using the Enhanced Commissioning Method.")]
110+
steps = [TestStep(1, "TH subscribes to PartsList attribute of the Descriptor cluster of DUT_FSA endpoint 0."),
111+
TestStep(2, "Follow manufacturer provided instructions to have DUT_FSA commission TH_SERVER"),
112+
TestStep(3, "TH waits up to 30 seconds for subscription report from the PartsList attribute of the Descriptor to contain new endpoint"),
113+
114+
TestStep(4, "TH uses DUT to open commissioning window to TH_SERVER"),
115+
TestStep(5, "TH commissions TH_SERVER"),
116+
TestStep(6, "TH reads all attributes in Basic Information cluster from TH_SERVER directly"),
117+
TestStep(7, "TH reads all attributes in the Bridged Device Basic Information cluster on new endpoint identified in step 3 from the DUT_FSA")]
43118
return steps
44119

45120
@property
@@ -49,99 +124,60 @@ def default_timeout(self) -> int:
49124
@async_test_body
50125
async def test_TC_MCORE_FS_1_2(self):
51126
self.is_ci = self.check_pics('PICS_SDK_CI_ONLY')
127+
52128
min_report_interval_sec = self.user_params.get("min_report_interval_sec", 0)
53-
max_report_interval_sec = self.user_params.get("max_report_interval_sec", 2)
54-
report_waiting_timeout_delay_sec = self.user_params.get("report_waiting_timeout_delay_sec", 10)
129+
max_report_interval_sec = self.user_params.get("max_report_interval_sec", 30)
130+
th_server_port = self.user_params.get("th_server_port", 5543)
131+
self._th_server_app_path = self.user_params.get("th_server_app_path", None)
132+
if not self._th_server_app_path:
133+
asserts.fail('This test requires a TH_SERVER app. Specify app path with --string-arg th_server_app_path:<path_to_app>')
134+
if not os.path.exists(self._th_server_app_path):
135+
asserts.fail(f'The path {self._th_server_app_path} does not exist')
55136

56137
self.step(1)
57-
58-
# Subscribe to the UniqueIDs
59-
unique_id_queue = queue.Queue()
138+
# Subscribe to the PartsList
139+
root_endpoint = 0
60140
subscription_contents = [
61-
(Clusters.BridgedDeviceBasicInformation.Attributes.UniqueID) # On all endpoints
141+
(root_endpoint, Clusters.Descriptor.Attributes.PartsList)
62142
]
63143
sub = await self.default_controller.ReadAttribute(
64144
nodeid=self.dut_node_id,
65145
attributes=subscription_contents,
66146
reportInterval=(min_report_interval_sec, max_report_interval_sec),
67147
keepSubscriptions=False
68148
)
149+
150+
parts_list_queue = queue.Queue()
69151
attribute_handler = AttributeChangeAccumulator(
70-
name=self.default_controller.name, expected_attribute=Clusters.BridgedDeviceBasicInformation.Attributes.UniqueID, output=unique_id_queue)
152+
name=self.default_controller.name, expected_attribute=Clusters.Descriptor.Attributes.PartsList, output=parts_list_queue)
71153
sub.SetAttributeUpdateCallback(attribute_handler)
154+
cached_attributes = sub.GetAttributes()
155+
step_1_dut_parts_list = cached_attributes[root_endpoint][Clusters.Descriptor][Clusters.Descriptor.Attributes.PartsList]
72156

73-
logging.info("Waiting for First BridgedDeviceBasicInformation.")
74-
start_time = time.time()
75-
elapsed = 0
76-
time_remaining = report_waiting_timeout_delay_sec
77-
78-
th_sed_dut_bdbi_endpoint = -1
79-
th_sed_dut_unique_id = -1
80-
81-
while time_remaining > 0 and th_sed_dut_bdbi_endpoint < 0:
82-
try:
83-
item = unique_id_queue.get(block=True, timeout=time_remaining)
84-
endpoint, attribute, value = item['endpoint'], item['attribute'], item['value']
85-
86-
# Record arrival of an expected subscription change when seen
87-
if attribute == Clusters.BridgedDeviceBasicInformation.Attributes.UniqueID:
88-
th_sed_dut_bdbi_endpoint = endpoint
89-
th_sed_dut_unique_id = value
90-
91-
except queue.Empty:
92-
# No error, we update timeouts and keep going
93-
pass
94-
95-
elapsed = time.time() - start_time
96-
time_remaining = report_waiting_timeout_delay_sec - elapsed
97-
98-
asserts.assert_greater(th_sed_dut_bdbi_endpoint, 0, "Failed to find any BDBI instances with UniqueID present.")
99-
logging.info("Found BDBI with UniqueID (%d) on endpoint %d." % th_sed_dut_unique_id, th_sed_dut_bdbi_endpoint)
157+
asserts.assert_true(type_matches(step_1_dut_parts_list, list), "PartsList is expected to be a list")
100158

101159
self.step(2)
102-
103-
self.sync_passcode = 20202024
104-
self.th_sed_dut_discriminator = 2222
105-
cmd = Clusters.AdministratorCommissioning.Commands.OpenCommissioningWindow(commissioningTimeout=3*60,
106-
PAKEPasscodeVerifier=b"+w1qZQR05Zn0bc2LDyNaDAhsrhDS5iRHPTN10+EmNx8E2OpIPC4SjWRDQVOgqcbnXdYMlpiZ168xLBqn1fx9659gGK/7f9Yc6GxpoJH8kwAUYAYyLGsYeEBt1kL6kpXjgA==",
107-
discriminator=self.th_sed_dut_discriminator,
108-
iterations=10000, salt=base64.b64encode(bytes('SaltyMcSalterson', 'utf-8')))
109-
await self.send_single_cmd(cmd, endpoint=th_sed_dut_bdbi_endpoint, timedRequestTimeoutMs=5000)
110-
111-
logging.info("Commissioning Window open for TH_SED_DUT.")
160+
setup_params = await self._create_th_server(th_server_port)
161+
self._ask_for_vendor_commissioning_ux_operation(setup_params)
112162

113163
self.step(3)
114-
115-
self.th_sed_dut_nodeid = 1111
116-
await self.TH_server_controller.CommissionOnNetwork(nodeId=self.th_sed_dut_nodeid, setupPinCode=self.sync_passcode, filterType=ChipDeviceCtrl.DiscoveryFilterType.LONG_DISCRIMINATOR, filter=self.th_sed_dut_discriminator)
117-
logging.info("Commissioning TH_SED_DUT complete")
118-
119-
self.step(4)
120-
if not self.is_ci:
121-
self.wait_for_user_input(
122-
"Commission TH_SED_DUT onto DUT_FSA’s fabric using the manufacturer specified mechanism. (ensure Synchronization is enabled.)")
123-
else:
124-
logging.info("Stopping after step 3 while running in CI to avoid manual steps.")
125-
return
126-
127-
self.step(5)
128-
129-
th_sed_later_bdbi_endpoint = -1
130-
th_sed_later_unique_id = -1
131-
logging.info("Waiting for Second BridgedDeviceBasicInformation.")
164+
report_waiting_timeout_delay_sec = 30
165+
logging.info("Waiting for update to PartsList.")
132166
start_time = time.time()
133167
elapsed = 0
134168
time_remaining = report_waiting_timeout_delay_sec
135169

136-
while time_remaining > 0 and th_sed_later_bdbi_endpoint < 0:
170+
parts_list_endpoint_count_from_step_1 = len(step_1_dut_parts_list)
171+
step_3_dut_parts_list = None
172+
while time_remaining > 0:
137173
try:
138-
item = unique_id_queue.get(block=True, timeout=time_remaining)
174+
item = parts_list_queue.get(block=True, timeout=time_remaining)
139175
endpoint, attribute, value = item['endpoint'], item['attribute'], item['value']
140176

141177
# Record arrival of an expected subscription change when seen
142-
if attribute == Clusters.BridgedDeviceBasicInformation.Attributes.UniqueID and endpoint != th_sed_dut_bdbi_endpoint and th_sed_later_unique_id != th_sed_dut_unique_id:
143-
th_sed_later_bdbi_endpoint = endpoint
144-
th_sed_later_unique_id = value
178+
if endpoint == root_endpoint and attribute == Clusters.Descriptor.Attributes.PartsList and len(value) > parts_list_endpoint_count_from_step_1:
179+
step_3_dut_parts_list = value
180+
break
145181

146182
except queue.Empty:
147183
# No error, we update timeouts and keep going
@@ -150,26 +186,63 @@ async def test_TC_MCORE_FS_1_2(self):
150186
elapsed = time.time() - start_time
151187
time_remaining = report_waiting_timeout_delay_sec - elapsed
152188

153-
asserts.assert_greater(th_sed_later_bdbi_endpoint, 0, "Failed to find any BDBI instances with UniqueID present.")
154-
logging.info("Found another BDBI with UniqueID (%d) on endpoint %d." % th_sed_later_unique_id, th_sed_later_bdbi_endpoint)
189+
asserts.assert_not_equal(step_3_dut_parts_list, None, "Timed out getting updated PartsList with new endpoint")
190+
set_of_step_1_parts_list_endpoint = set(step_1_dut_parts_list)
191+
set_of_step_3_parts_list_endpoint = set(step_3_dut_parts_list)
192+
unique_endpoints_set = set_of_step_3_parts_list_endpoint - set_of_step_1_parts_list_endpoint
193+
asserts.assert_equal(len(unique_endpoints_set), 1, "Expected only one new endpoint")
194+
newly_added_endpoint = list(unique_endpoints_set)[0]
155195

156-
self.step(6)
196+
self.step(4)
157197

158-
self.th_sed_later_discriminator = 3333
159-
# min commissioning timeout is 3*60 seconds, so use that even though the command said 30.
198+
discriminator = 3840
199+
passcode = 20202021
200+
salt = secrets.token_bytes(16)
201+
iterations = 2000
202+
verifier = _generate_verifier(passcode, salt, iterations)
203+
204+
# min commissioning timeout is 3*60 seconds
160205
cmd = Clusters.AdministratorCommissioning.Commands.OpenCommissioningWindow(commissioningTimeout=3*60,
161-
PAKEPasscodeVerifier=b"+w1qZQR05Zn0bc2LDyNaDAhsrhDS5iRHPTN10+EmNx8E2OpIPC4SjWRDQVOgqcbnXdYMlpiZ168xLBqn1fx9659gGK/7f9Yc6GxpoJH8kwAUYAYyLGsYeEBt1kL6kpXjgA==",
162-
discriminator=self.th_sed_later_discriminator,
163-
iterations=10000, salt=base64.b64encode(bytes('SaltyMcSalterson', 'utf-8')))
164-
await self.send_single_cmd(cmd, endpoint=th_sed_later_bdbi_endpoint, timedRequestTimeoutMs=5000)
206+
PAKEPasscodeVerifier=verifier,
207+
discriminator=discriminator,
208+
iterations=iterations,
209+
salt=salt)
210+
await self.send_single_cmd(cmd, dev_ctrl=self.default_controller, node_id=self.dut_node_id, endpoint=newly_added_endpoint, timedRequestTimeoutMs=5000)
165211

166-
logging.info("Commissioning Window open for TH_SED_L.")
212+
self.step(5)
213+
self.th_server_local_nodeid = 1111
214+
await self.default_controller.CommissionOnNetwork(nodeId=self.th_server_local_nodeid, setupPinCode=passcode, filterType=ChipDeviceCtrl.DiscoveryFilterType.LONG_DISCRIMINATOR, filter=discriminator)
167215

168-
self.step(7)
216+
self.step(6)
217+
th_server_directly_read_result = await self.default_controller.ReadAttribute(self.th_server_local_nodeid, [(root_endpoint, Clusters.BasicInformation)])
218+
th_server_basic_info = th_server_directly_read_result[root_endpoint][Clusters.BasicInformation]
169219

170-
self.th_sed_later_nodeid = 2222
171-
await self.TH_server_controller.CommissionOnNetwork(nodeId=self.th_sed_later_nodeid, setupPinCode=self.sync_passcode, filterType=ChipDeviceCtrl.DiscoveryFilterType.LONG_DISCRIMINATOR, filter=self.th_sed_later_discriminator)
172-
logging.info("Commissioning TH_SED_L complete")
220+
self.step(7)
221+
dut_read = await self.default_controller.ReadAttribute(self.dut_node_id, [(newly_added_endpoint, Clusters.BridgedDeviceBasicInformation)])
222+
bridged_info_for_th_server = dut_read[newly_added_endpoint][Clusters.BridgedDeviceBasicInformation]
223+
basic_info_attr = Clusters.BasicInformation.Attributes
224+
bridged_device_info_attr = Clusters.BridgedDeviceBasicInformation.Attributes
225+
Clusters.BasicInformation.Attributes
226+
asserts.assert_equal(th_server_basic_info[basic_info_attr.VendorName],
227+
bridged_info_for_th_server[bridged_device_info_attr.VendorName], "VendorName incorrectly reported by DUT")
228+
asserts.assert_equal(th_server_basic_info[basic_info_attr.VendorID],
229+
bridged_info_for_th_server[bridged_device_info_attr.VendorID], "VendorID incorrectly reported by DUT")
230+
asserts.assert_equal(th_server_basic_info[basic_info_attr.ProductName],
231+
bridged_info_for_th_server[bridged_device_info_attr.ProductName], "ProductName incorrectly reported by DUT")
232+
asserts.assert_equal(th_server_basic_info[basic_info_attr.ProductID],
233+
bridged_info_for_th_server[bridged_device_info_attr.ProductID], "ProductID incorrectly reported by DUT")
234+
asserts.assert_equal(th_server_basic_info[basic_info_attr.NodeLabel],
235+
bridged_info_for_th_server[bridged_device_info_attr.NodeLabel], "NodeLabel incorrectly reported by DUT")
236+
asserts.assert_equal(th_server_basic_info[basic_info_attr.HardwareVersion],
237+
bridged_info_for_th_server[bridged_device_info_attr.HardwareVersion], "HardwareVersion incorrectly reported by DUT")
238+
asserts.assert_equal(th_server_basic_info[basic_info_attr.HardwareVersionString],
239+
bridged_info_for_th_server[bridged_device_info_attr.HardwareVersionString], "HardwareVersionString incorrectly reported by DUT")
240+
asserts.assert_equal(th_server_basic_info[basic_info_attr.SoftwareVersion],
241+
bridged_info_for_th_server[bridged_device_info_attr.SoftwareVersion], "SoftwareVersion incorrectly reported by DUT")
242+
asserts.assert_equal(th_server_basic_info[basic_info_attr.SoftwareVersionString],
243+
bridged_info_for_th_server[bridged_device_info_attr.SoftwareVersionString], "SoftwareVersionString incorrectly reported by DUT")
244+
asserts.assert_equal(th_server_basic_info[basic_info_attr.UniqueID],
245+
bridged_info_for_th_server[bridged_device_info_attr.UniqueID], "UniqueID incorrectly reported by DUT")
173246

174247

175248
if __name__ == "__main__":

0 commit comments

Comments
 (0)