19
19
# for details about the block below.
20
20
#
21
21
22
- import base64
22
+ import hashlib
23
23
import logging
24
+ import os
24
25
import queue
26
+ import secrets
27
+ import signal
28
+ import struct
29
+ import subprocess
25
30
import time
31
+ import uuid
32
+ from dataclasses import dataclass
26
33
27
34
import chip .clusters as Clusters
28
35
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
30
38
from mobly import asserts
31
39
from TC_SC_3_6 import AttributeChangeAccumulator
32
40
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
+
33
61
34
62
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
+
35
109
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" )]
43
118
return steps
44
119
45
120
@property
@@ -49,99 +124,60 @@ def default_timeout(self) -> int:
49
124
@async_test_body
50
125
async def test_TC_MCORE_FS_1_2 (self ):
51
126
self .is_ci = self .check_pics ('PICS_SDK_CI_ONLY' )
127
+
52
128
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' )
55
136
56
137
self .step (1 )
57
-
58
- # Subscribe to the UniqueIDs
59
- unique_id_queue = queue .Queue ()
138
+ # Subscribe to the PartsList
139
+ root_endpoint = 0
60
140
subscription_contents = [
61
- (Clusters .BridgedDeviceBasicInformation .Attributes .UniqueID ) # On all endpoints
141
+ (root_endpoint , Clusters .Descriptor .Attributes .PartsList )
62
142
]
63
143
sub = await self .default_controller .ReadAttribute (
64
144
nodeid = self .dut_node_id ,
65
145
attributes = subscription_contents ,
66
146
reportInterval = (min_report_interval_sec , max_report_interval_sec ),
67
147
keepSubscriptions = False
68
148
)
149
+
150
+ parts_list_queue = queue .Queue ()
69
151
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 )
71
153
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 ]
72
156
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" )
100
158
101
159
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 )
112
162
113
163
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." )
132
166
start_time = time .time ()
133
167
elapsed = 0
134
168
time_remaining = report_waiting_timeout_delay_sec
135
169
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 :
137
173
try :
138
- item = unique_id_queue .get (block = True , timeout = time_remaining )
174
+ item = parts_list_queue .get (block = True , timeout = time_remaining )
139
175
endpoint , attribute , value = item ['endpoint' ], item ['attribute' ], item ['value' ]
140
176
141
177
# 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
145
181
146
182
except queue .Empty :
147
183
# No error, we update timeouts and keep going
@@ -150,26 +186,63 @@ async def test_TC_MCORE_FS_1_2(self):
150
186
elapsed = time .time () - start_time
151
187
time_remaining = report_waiting_timeout_delay_sec - elapsed
152
188
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 ]
155
195
156
- self .step (6 )
196
+ self .step (4 )
157
197
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
160
205
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 )
165
211
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 )
167
215
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 ]
169
219
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" )
173
246
174
247
175
248
if __name__ == "__main__" :
0 commit comments