Skip to content

Commit 4c39f7e

Browse files
gvargas-csacecille
andauthored
TC-OPCREDS-3.2: Add (#32182)
* chore(TC_OPCREDS-3.2): skeleton base class * chore(TC_OPCREDS_3.2): NOCResponse command * chore(TC_OPCREDS_3.2): get currentFabric * chore(TC_OPCREDS_3.2): print some status values * TC-OPCREDS-3.2: Re-work test This test was difficult to automate and included a factory reset. This automation start is used to generate the test plans. The intent of this test is to confirm that the returned fabric index matches the fabric index returned from the AddNOC command, and that the read of fabric-filtered attributes matches to the calling fabric. The rest of the test is covered by OPCREDS-3.1 so was removed to simplify. NOTE: test not yet implemented, uploaded for test plan generation purposes only. - Properly use Test Step and Expected outcome columns - Remove checks for NOCs matching - this is checked in OPCREDS-3.1 - Remove factory reset and commission two new fabrics - Use commissioning over CASE so we can more easily return the fabric index * chore(TC_OPCREDS_3.2): included new steps (1-6) Just need to add validation for CR3 and remove all fabrics. Also it need clean up (remove prints, unused variables, etc) * chore(TC_OPCREDS_3.2): all steps are implemtented * chore(TC_OPCREDS_3.2): restyled by isort * chore(TC_OPCREDS_3_2): fix error from python linter (code-lints) * chore(TC_OPCREDS_3.2): import restyled by isort * chore(TC_OPCREDS_3.2): fixed the vendorID for prevent fail * chore(TC_OPCREDS_3.2): add test to CI Workflow * chore(TC_OPCREDS_3.2): off auto-format and change the return values label * chore(TC_OPCREDS_3.2): remove if-else validation instead used assert_equal * chore(TC_OPCREDS_3.2): spacing adjustment for better readability * chore(TC_OPCREDS_3.2): identation adjustment for better readability * chore(TC_OPCREDS_3.2): restyled patch identation adjustment * chore(TC_OPCREDS_3.2): reverted addition of test --------- Co-authored-by: cecille <cecille@google.com>
1 parent 92a322d commit 4c39f7e

File tree

4 files changed

+290
-15
lines changed

4 files changed

+290
-15
lines changed

.github/workflows/tests.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,7 @@ jobs:
536536
scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --script "src/python_testing/TestMatterTestingSupport.py" --script-args "--trace-to json:out/trace_data/test-{SCRIPT_BASE_NAME}.json --trace-to perfetto:out/trace_data/test-{SCRIPT_BASE_NAME}.perfetto"'
537537
scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --script "src/python_testing/TestSpecParsingSupport.py" --script-args "--trace-to json:out/trace_data/test-{SCRIPT_BASE_NAME}.json --trace-to perfetto:out/trace_data/test-{SCRIPT_BASE_NAME}.perfetto"'
538538
scripts/run_in_python_env.sh out/venv './scripts/tests/TestTimeSyncTrustedTimeSourceRunner.py'
539+
scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:out/trace_data/app-{SCRIPT_BASE_NAME}.json" --script "src/python_testing/TC_OPCREDS_3_2.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --PICS src/app/tests/suites/certification/ci-pics-values --trace-to json:out/trace_data/test-{SCRIPT_BASE_NAME}.json --trace-to perfetto:out/trace_data/test-{SCRIPT_BASE_NAME}.perfetto"'
539540
scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:out/trace_data/app-{SCRIPT_BASE_NAME}.json" --script "src/python_testing/TC_OPSTATE_2_1.py" --script-args "--endpoint 1 --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --PICS src/app/tests/suites/certification/ci-pics-values --trace-to json:out/trace_data/test-{SCRIPT_BASE_NAME}.json --trace-to perfetto:out/trace_data/test-{SCRIPT_BASE_NAME}.perfetto"'
540541
scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:out/trace_data/app-{SCRIPT_BASE_NAME}.json" --script "src/python_testing/TC_OPSTATE_2_2.py" --script-args "--endpoint 1 --int-arg PIXIT.WAITTIME.COUNTDOWN:5 --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --PICS src/app/tests/suites/certification/ci-pics-values --trace-to json:out/trace_data/test-{SCRIPT_BASE_NAME}.json --trace-to perfetto:out/trace_data/test-{SCRIPT_BASE_NAME}.perfetto"'
541542
scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:out/trace_data/app-{SCRIPT_BASE_NAME}.json" --script "src/python_testing/TC_OPSTATE_2_3.py" --script-args "--endpoint 1 --int-arg PIXIT.WAITTIME.COUNTDOWN:5 --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --PICS src/app/tests/suites/certification/ci-pics-values --trace-to json:out/trace_data/test-{SCRIPT_BASE_NAME}.json --trace-to perfetto:out/trace_data/test-{SCRIPT_BASE_NAME}.perfetto"'

src/controller/python/chip/utils/CommissioningBuildingBlocks.py

+21-15
Original file line numberDiff line numberDiff line change
@@ -156,12 +156,14 @@ async def AddNOCForNewFabricFromExisting(commissionerDevCtrl, newFabricDevCtrl,
156156
newNodeId (int): Node ID to use for the target node on the new fabric.
157157
158158
Return:
159-
bool: True if successful, False otherwise.
159+
tuple: (bool, Optional[nocResp], Optional[rcacResp]: True if successful, False otherwise, along with nocResp, rcacResp value.
160160
161161
'''
162+
nocResp = None
163+
162164
resp = await commissionerDevCtrl.SendCommand(existingNodeId, 0, generalCommissioning.Commands.ArmFailSafe(60))
163165
if resp.errorCode is not generalCommissioning.Enums.CommissioningErrorEnum.kOk:
164-
return False
166+
return False, nocResp
165167

166168
csrForAddNOC = await commissionerDevCtrl.SendCommand(existingNodeId, 0, opCreds.Commands.CSRRequest(CSRNonce=os.urandom(32)))
167169

@@ -171,31 +173,35 @@ async def AddNOCForNewFabricFromExisting(commissionerDevCtrl, newFabricDevCtrl,
171173
chainForAddNOC.nocBytes is None or chainForAddNOC.ipkBytes is None):
172174
# Expiring the failsafe timer in an attempt to clean up.
173175
await commissionerDevCtrl.SendCommand(existingNodeId, 0, generalCommissioning.Commands.ArmFailSafe(0))
174-
return False
176+
return False, nocResp
175177

176178
await commissionerDevCtrl.SendCommand(existingNodeId, 0, opCreds.Commands.AddTrustedRootCertificate(chainForAddNOC.rcacBytes))
177-
resp = await commissionerDevCtrl.SendCommand(existingNodeId,
178-
0,
179-
opCreds.Commands.AddNOC(chainForAddNOC.nocBytes,
180-
chainForAddNOC.icacBytes,
181-
chainForAddNOC.ipkBytes,
182-
newFabricDevCtrl.nodeId,
183-
newFabricDevCtrl.fabricAdmin.vendorId))
184-
if resp.statusCode is not opCreds.Enums.NodeOperationalCertStatusEnum.kOk:
179+
nocResp = await commissionerDevCtrl.SendCommand(existingNodeId,
180+
0,
181+
opCreds.Commands.AddNOC(chainForAddNOC.nocBytes,
182+
chainForAddNOC.icacBytes,
183+
chainForAddNOC.ipkBytes,
184+
newFabricDevCtrl.nodeId,
185+
newFabricDevCtrl.fabricAdmin.vendorId))
186+
187+
rcacResp = chainForAddNOC.rcacBytes
188+
189+
if nocResp.statusCode is not opCreds.Enums.NodeOperationalCertStatusEnum.kOk:
185190
# Expiring the failsafe timer in an attempt to clean up.
186191
await commissionerDevCtrl.SendCommand(existingNodeId, 0, generalCommissioning.Commands.ArmFailSafe(0))
187-
return False
192+
return False, nocResp
188193

189194
resp = await newFabricDevCtrl.SendCommand(newNodeId, 0, generalCommissioning.Commands.CommissioningComplete())
195+
190196
if resp.errorCode is not generalCommissioning.Enums.CommissioningErrorEnum.kOk:
191197
# Expiring the failsafe timer in an attempt to clean up.
192198
await commissionerDevCtrl.SendCommand(existingNodeId, 0, generalCommissioning.Commands.ArmFailSafe(0))
193-
return False
199+
return False, nocResp
194200

195201
if not await _IsNodeInFabricList(newFabricDevCtrl, newNodeId):
196-
return False
202+
return False, nocResp
197203

198-
return True
204+
return True, nocResp, rcacResp
199205

200206

201207
async def UpdateNOC(devCtrl, existingNodeId, newNodeId):

src/python_testing/TC_OPCREDS_3_2.py

+190
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
#
2+
# Copyright (c) 2024 Project CHIP Authors
3+
# All rights reserved.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
18+
import chip.clusters as Clusters
19+
from chip.tlv import TLVReader
20+
from chip.utils import CommissioningBuildingBlocks
21+
from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main
22+
from mobly import asserts
23+
from test_plan_support import (commission_from_existing, commission_if_required, read_attribute, remove_fabric,
24+
verify_commissioning_successful, verify_success)
25+
26+
27+
def verify_fabric(controller: str) -> str:
28+
return (f"- Verify there is one entry returned. Verify FabricIndex matches `fabric_index_{controller}`.\n"
29+
f"- Verify the RootPublicKey matches the public key for rcac_{controller}.\n"
30+
f"- Verify the VendorID matches the vendor ID for {controller}.\n"
31+
f"- Verify the FabricID matches the fabricID for {controller}")
32+
33+
34+
class TC_OPCREDS_3_2(MatterBaseTest):
35+
def desc_TC_OPCREDS_3_2(self):
36+
return " Attribute-CurrentFabricIndex validation [DUTServer]"
37+
38+
def steps_TC_OPCREDS_3_2(self):
39+
return [TestStep(0, commission_if_required('CR1'), is_commissioning=True),
40+
TestStep(1, f"{commission_from_existing('CR1', 'CR2')}\n. Save the FabricIndex from the NOCResponse as `fabric_index_CR2`.",
41+
verify_commissioning_successful()),
42+
TestStep(2, f"{commission_from_existing('CR1', 'CR3')}\n. Save the FabricIndex from the NOCResponse as `fabric_index_CR3`.",
43+
verify_commissioning_successful()),
44+
TestStep(3, f"CR2 {read_attribute('CurrentFabricIndex')}",
45+
"Verify the returned value is `fabric_index_CR2`"),
46+
TestStep(4, f"CR3 {read_attribute('CurrentFabricIndex')}",
47+
"Verify the returned value is `fabric_index_CR3`"),
48+
TestStep(
49+
5, f"CR2 {read_attribute('Fabrics')} using a fabric-filtered read", verify_fabric('CR2')),
50+
TestStep(
51+
6, f"CR3 {read_attribute('Fabrics')} using a fabric-filtered read", verify_fabric('CR3')),
52+
TestStep(7, remove_fabric(
53+
'fabric_index_CR2', 'CR1'), verify_success()),
54+
TestStep(8, remove_fabric(
55+
'fabric_index_CR3', 'CR1'), verify_success()),
56+
]
57+
58+
@async_test_body
59+
async def test_TC_OPCREDS_3_2(self):
60+
opcreds = Clusters.OperationalCredentials
61+
62+
self.step(0)
63+
64+
self.step(1)
65+
dev_ctrl = self.default_controller
66+
67+
new_certificate_authority = self.certificate_authority_manager.NewCertificateAuthority()
68+
cr2_vid = 0xFFF2
69+
cr2_fabricId = 2222
70+
cr2_new_fabric_admin = new_certificate_authority.NewFabricAdmin(
71+
vendorId=cr2_vid, fabricId=cr2_fabricId)
72+
cr2_nodeid = self.default_controller.nodeId+1
73+
cr2_dut_node_id = self.dut_node_id+1
74+
75+
cr2_new_admin_ctrl = cr2_new_fabric_admin.NewController(
76+
nodeId=cr2_nodeid)
77+
success, nocResp, rcacResp = await CommissioningBuildingBlocks.AddNOCForNewFabricFromExisting(
78+
commissionerDevCtrl=dev_ctrl, newFabricDevCtrl=cr2_new_admin_ctrl,
79+
existingNodeId=self.dut_node_id, newNodeId=cr2_dut_node_id
80+
)
81+
82+
fabric_index_CR2 = nocResp.fabricIndex
83+
tlvReaderRCAC_CR2 = TLVReader(rcacResp).get()["Any"]
84+
rcac_CR2 = tlvReaderRCAC_CR2[9] # public key is field 9
85+
86+
self.step(2)
87+
new_certificate_authority = self.certificate_authority_manager.NewCertificateAuthority()
88+
cr3_vid = 0xFFF3
89+
cr3_fabricId = 3333
90+
cr3_new_fabric_admin = new_certificate_authority.NewFabricAdmin(
91+
vendorId=cr3_vid, fabricId=cr3_fabricId)
92+
cr3_nodeid = self.default_controller.nodeId+2
93+
cr3_dut_node_id = self.dut_node_id+2
94+
95+
cr3_new_admin_ctrl = cr3_new_fabric_admin.NewController(
96+
nodeId=cr3_nodeid)
97+
success, nocResp, rcacResp = await CommissioningBuildingBlocks.AddNOCForNewFabricFromExisting(
98+
commissionerDevCtrl=dev_ctrl, newFabricDevCtrl=cr3_new_admin_ctrl,
99+
existingNodeId=self.dut_node_id, newNodeId=cr3_dut_node_id
100+
)
101+
102+
fabric_index_CR3 = nocResp.fabricIndex
103+
tlvReaderRCAC_CR3 = TLVReader(rcacResp).get()["Any"]
104+
rcac_CR3 = tlvReaderRCAC_CR3[9]
105+
106+
self.step(3)
107+
cr2_read_fabricIndex = await self.read_single_attribute_check_success(
108+
dev_ctrl=cr2_new_admin_ctrl,
109+
node_id=cr2_dut_node_id,
110+
cluster=opcreds,
111+
attribute=opcreds.Attributes.CurrentFabricIndex
112+
)
113+
114+
asserts.assert_equal(fabric_index_CR2, cr2_read_fabricIndex,
115+
"Fail fabric_index_CR2 is not equal to read fabricIndex from CR2")
116+
117+
self.step(4)
118+
cr3_read_fabricIndex = await self.read_single_attribute_check_success(
119+
dev_ctrl=cr3_new_admin_ctrl,
120+
node_id=cr3_dut_node_id,
121+
cluster=opcreds,
122+
attribute=opcreds.Attributes.CurrentFabricIndex
123+
)
124+
125+
asserts.assert_equal(fabric_index_CR3, cr3_read_fabricIndex,
126+
"Fail fabric_index_CR3 is not equal to read fabricIndex from CR3")
127+
128+
self.step(5)
129+
cr2_fabric = await self.read_single_attribute_check_success(
130+
dev_ctrl=cr2_new_admin_ctrl,
131+
node_id=cr2_dut_node_id,
132+
cluster=opcreds,
133+
attribute=opcreds.Attributes.Fabrics,
134+
fabric_filtered=True
135+
)
136+
137+
for fabric in cr2_fabric:
138+
cr2_fabric_fabricIndex = fabric.fabricIndex
139+
cr2_fabric_rootPublicKey = fabric.rootPublicKey
140+
cr2_fabric_vendorId = fabric.vendorID
141+
cr2_fabric_fabricId = fabric.fabricID
142+
143+
asserts.assert_equal(cr2_fabric_fabricIndex,
144+
fabric_index_CR2, "Unexpected CR2 fabric index")
145+
asserts.assert_equal(cr2_fabric_rootPublicKey, rcac_CR2,
146+
"Unexpected RootPublicKey does not match with rcac_CR2")
147+
asserts.assert_equal(cr2_fabric_vendorId, cr2_vid,
148+
"Unexpected vendorId does not match with CR2 VendorID")
149+
asserts.assert_equal(cr2_fabric_fabricId, cr2_fabricId,
150+
"Unexpected fabricId does not match with CR2 fabricID")
151+
152+
self.step(6)
153+
cr3_fabric = await self.read_single_attribute_check_success(
154+
dev_ctrl=cr3_new_admin_ctrl,
155+
node_id=cr3_dut_node_id,
156+
cluster=opcreds,
157+
attribute=opcreds.Attributes.Fabrics,
158+
fabric_filtered=True
159+
)
160+
161+
for fabric in cr3_fabric:
162+
cr3_fabric_fabricIndex = fabric.fabricIndex
163+
cr3_fabric_rootPublicKey = fabric.rootPublicKey
164+
cr3_fabric_vendorId = fabric.vendorID
165+
cr3_fabric_fabricId = fabric.fabricID
166+
167+
asserts.assert_equal(cr3_fabric_fabricIndex,
168+
fabric_index_CR3, "Unexpected CR3 fabric index")
169+
asserts.assert_equal(cr3_fabric_rootPublicKey, rcac_CR3,
170+
"Unexpected RootPublicKey does not match with rcac_CR3")
171+
asserts.assert_equal(cr3_fabric_vendorId, cr3_vid,
172+
"Unexpected vendorId does not match with CR3 VendorID")
173+
asserts.assert_equal(cr3_fabric_fabricId, cr3_fabricId,
174+
"Unexpected fabricId does not match with CR3 fabricID")
175+
176+
self.step(7)
177+
cmd = opcreds.Commands.RemoveFabric(fabric_index_CR2)
178+
resp = await self.send_single_cmd(cmd=cmd)
179+
asserts.assert_equal(
180+
resp.statusCode, opcreds.Enums.NodeOperationalCertStatusEnum.kOk)
181+
182+
self.step(8)
183+
cmd = opcreds.Commands.RemoveFabric(fabric_index_CR3)
184+
resp = await self.send_single_cmd(cmd=cmd)
185+
asserts.assert_equal(
186+
resp.statusCode, opcreds.Enums.NodeOperationalCertStatusEnum.kOk)
187+
188+
189+
if __name__ == "__main__":
190+
default_matter_test_main()
+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#
2+
# Copyright (c) 2024 Project CHIP Authors
3+
# All rights reserved.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
import typing
18+
19+
20+
def read_attribute(attribute: str, cluster: typing.Optional[str] = None):
21+
attr = f'reads the {attribute} attribute'
22+
if cluster:
23+
return f'{attr} from {cluster}'
24+
else:
25+
return attr
26+
27+
28+
def save_as(val: str) -> str:
29+
return f' and saves the value as {val}'
30+
31+
32+
def verify_status(status: str) -> str:
33+
return f'Verify DUT responds w/ status {status}'
34+
35+
36+
def verify_success() -> str:
37+
return verify_status('SUCCESS')
38+
39+
# -----------------------
40+
# Commissioning strings
41+
# -----------------------
42+
43+
44+
def commission_if_required(controller: typing.Optional[str] = None) -> str:
45+
controller_str = f'to {controller} ' if controller is not None else ''
46+
return f'Commission DUT {controller_str}if not already done'
47+
48+
49+
def commission_from_existing(existing_controller_name: str, new_controller_name: str) -> str:
50+
# NOTE to implementers: This text corresponds to the actions taken by CommissioningBuildingBlocks.AddNOCForNewFabricFromExisting.
51+
# This function should be used in the TestSteps description when you use that function.
52+
# AddNOCForNewFabricFromExisting is used when the generated certificates are required for use in the test.
53+
# It written one step so we can just use the function directly without needing to annotate the sub-steps for the TH.
54+
return (f'Create a new controller on a new fabric called {new_controller_name}.\n'
55+
f'Commission the new controller from {existing_controller_name} as follows:\n\n'
56+
f'- {existing_controller_name} sends an ArmFailsafe command, followed by a CSRRequest command.\n'
57+
f'- Generate credentials on {new_controller_name} using the returned CSR.\n'
58+
f'- Save the RCAC as `rcac_{new_controller_name}. Save the ICAC as `icac_{new_controller_name}`. Save the NOC as `noc_{new_controller_name}`. Save the IPK as ipk_{new_controller_name}.\n'
59+
f'- {existing_controller_name} sends the AddTrustedRootCertificate command with `rcac_{new_controller_name}`'
60+
f'- {existing_controller_name} sends the AddNOC command with the fields set as follows:\n'
61+
f' * NOCValue: `noc_{new_controller_name}`\n'
62+
f' * ICACValue: `icac_{new_controller_name}`\n'
63+
f' * IPKValue: `ipk_{new_controller_name}`\n'
64+
f' * CaseAdminSubject: {new_controller_name} node ID\n'
65+
f' * AdminVendorId: {new_controller_name} vendor ID\n'
66+
f'- {new_controller_name} connects over CASE and sends the commissioning complete command')
67+
68+
69+
def open_commissioning_window(controller: str = 'TH') -> str:
70+
return f'{controller} opens a commissioning window on the DUT'
71+
72+
73+
def remove_fabric(index_var: str, controller: str):
74+
return f'{controller} sends the RemoveFabric command to the Node Operational Credentials cluster with the FabricIndex set to {index_var}.'
75+
76+
77+
def verify_commissioning_successful() -> str:
78+
return 'Verify the commissioning is successful.'

0 commit comments

Comments
 (0)