Skip to content

Commit 9533925

Browse files
authored
IDM-10.2: Handle MACL feature (#35404)
* Reapply "TC-IDM-10.2: Add check for MACL (#35086)" (#35111) This reverts commit 796394f. * Change function name to more generic
1 parent a5d5f85 commit 9533925

File tree

4 files changed

+123
-0
lines changed

4 files changed

+123
-0
lines changed

.github/workflows/tests.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,7 @@ jobs:
522522
scripts/run_in_python_env.sh out/venv 'python3 ./src/python_testing/TestSpecParsingSupport.py'
523523
scripts/run_in_python_env.sh out/venv 'python3 ./src/python_testing/TestConformanceTest.py'
524524
scripts/run_in_python_env.sh out/venv 'python3 ./src/python_testing/TestConformanceSupport.py'
525+
scripts/run_in_python_env.sh out/venv 'python3 ./src/python_testing/TestConformanceTest.py'
525526
scripts/run_in_python_env.sh out/venv 'python3 ./src/python_testing/TestChoiceConformanceSupport.py'
526527
scripts/run_in_python_env.sh out/venv 'python3 ./src/python_testing/test_testing/test_IDM_10_4.py'
527528
scripts/run_in_python_env.sh out/venv 'python3 ./src/python_testing/test_testing/test_TC_SC_7_1.py'

src/python_testing/TC_DeviceConformance.py

+25
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,24 @@ async def setup_class_helper(self):
5050
self.xml_device_types, problems = build_xml_device_types()
5151
self.problems.extend(problems)
5252

53+
def _get_device_type_id(self, device_type_name: str) -> int:
54+
id = [id for id, dt in self.xml_device_types.items() if dt.name.lower() == device_type_name.lower()]
55+
if len(id) != 1:
56+
self.fail_current_test(f"Unable to find {device_type_name} device type")
57+
return id[0]
58+
59+
def _has_device_type_supporting_macl(self):
60+
# Currently this is just NIM. We may later be able to pull this from the device type scrape using the ManagedAclAllowed condition,
61+
# but these are not currently exposed directly by the device.
62+
allowed_ids = [self._get_device_type_id('network infrastructure manager')]
63+
for endpoint in self.endpoints_tlv.values():
64+
desc = Clusters.Descriptor
65+
device_types = [dt.deviceType for dt in endpoint[desc.id][desc.Attributes.DeviceTypeList.attribute_id]]
66+
if set(allowed_ids).intersection(set(device_types)):
67+
# TODO: it's unclear if this needs to be present on every endpoint. Right now, this assumes one is sufficient.
68+
return True
69+
return False
70+
5371
def check_conformance(self, ignore_in_progress: bool, is_ci: bool, allow_provisional: bool):
5472
problems = []
5573
success = True
@@ -120,6 +138,13 @@ def record_warning(location, problem):
120138
for f in feature_masks:
121139
location = AttributePathLocation(endpoint_id=endpoint_id, cluster_id=cluster_id,
122140
attribute_id=GlobalAttributeIds.FEATURE_MAP_ID)
141+
if cluster_id == Clusters.AccessControl.id and f == Clusters.AccessControl.Bitmaps.Feature.kManagedDevice:
142+
# Managed ACL is treated as a special case because it is only allowed if other endpoints support NIM and disallowed otherwise.
143+
if not self._has_device_type_supporting_macl():
144+
record_error(
145+
location=location, problem="MACL feature is disallowed if the a supported device type is not present")
146+
continue
147+
123148
if f not in self.xml_clusters[cluster_id].features.keys():
124149
record_error(location=location, problem=f'Unknown feature with mask 0x{f:02x}')
125150
continue

src/python_testing/TestConformanceTest.py

+96
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
from typing import Any
1919

2020
import chip.clusters as Clusters
21+
from conformance_support import ConformanceDecision
22+
from global_attribute_ids import GlobalAttributeIds
2123
from matter_testing_support import MatterBaseTest, async_test_body, default_matter_test_main
2224
from mobly import asserts
2325
from spec_parsing_support import build_xml_clusters, build_xml_device_types
@@ -109,6 +111,10 @@ def create_onoff_endpoint(endpoint: int) -> dict[int, dict[int, dict[int, Any]]]
109111
return endpoint_tlv
110112

111113

114+
def is_mandatory(conformance):
115+
return conformance(0, [], []).decision == ConformanceDecision.MANDATORY
116+
117+
112118
class TestConformanceSupport(MatterBaseTest, DeviceConformanceTests):
113119
def setup_class(self):
114120
self.xml_clusters, self.problems = build_xml_clusters()
@@ -135,6 +141,96 @@ async def test_provisional_cluster(self):
135141
success, problems = self.check_conformance(ignore_in_progress=False, is_ci=False, allow_provisional=False)
136142
asserts.assert_true(success, "Unexpected failure parsing endpoint with no clusters marked as provisional")
137143

144+
def _create_minimal_cluster(self, cluster_id: int) -> dict[int, Any]:
145+
attrs = {}
146+
attrs[GlobalAttributeIds.FEATURE_MAP_ID] = 0
147+
148+
mandatory_attributes = [id for id, a in self.xml_clusters[cluster_id].attributes.items() if is_mandatory(a.conformance)]
149+
for m in mandatory_attributes:
150+
# dummy versions - we're not using the values in this test
151+
attrs[m] = 0
152+
attrs[GlobalAttributeIds.ATTRIBUTE_LIST_ID] = mandatory_attributes
153+
mandatory_accepted_commands = [id for id, a in self.xml_clusters[cluster_id].accepted_commands.items()
154+
if is_mandatory(a.conformance)]
155+
attrs[GlobalAttributeIds.ACCEPTED_COMMAND_LIST_ID] = mandatory_accepted_commands
156+
mandatory_generated_commands = [id for id, a in self.xml_clusters[cluster_id].generated_commands.items()
157+
if is_mandatory(a.conformance)]
158+
attrs[GlobalAttributeIds.GENERATED_COMMAND_LIST_ID] = mandatory_generated_commands
159+
attrs[GlobalAttributeIds.CLUSTER_REVISION_ID] = self.xml_clusters[cluster_id].revision
160+
return attrs
161+
162+
def _create_minimal_dt(self, device_type_id: int) -> dict[int, dict[int, Any]]:
163+
''' Creates the internals of an endpoint_tlv with the minimal set of clusters, with the minimal set of attributes and commands. Global attributes only.
164+
Does NOT take into account overrides yet.
165+
'''
166+
endpoint_tlv = {}
167+
required_servers = [id for id, c in self.xml_device_types[device_type_id].server_clusters.items()
168+
if is_mandatory(c.conformance)]
169+
required_clients = [id for id, c in self.xml_device_types[device_type_id].client_clusters.items()
170+
if is_mandatory(c.conformance)]
171+
device_type_revision = self.xml_device_types[device_type_id].revision
172+
173+
for s in required_servers:
174+
endpoint_tlv[s] = self._create_minimal_cluster(s)
175+
176+
# Descriptor
177+
attr = Clusters.Descriptor.Attributes
178+
attrs = {}
179+
attrs[attr.FeatureMap.attribute_id] = 0
180+
attrs[attr.AcceptedCommandList.attribute_id] = []
181+
attrs[attr.GeneratedCommandList.attribute_id] = []
182+
attrs[attr.ClusterRevision.attribute_id] = self.xml_clusters[Clusters.Descriptor.id].revision
183+
attrs[attr.DeviceTypeList.attribute_id] = [
184+
Clusters.Descriptor.Structs.DeviceTypeStruct(deviceType=device_type_id, revision=device_type_revision)]
185+
attrs[attr.ServerList.attribute_id] = required_servers
186+
attrs[attr.ClientList.attribute_id] = required_clients
187+
attrs[attr.PartsList.attribute_id] = []
188+
attrs[attr.AttributeList.attribute_id] = []
189+
attrs[attr.AttributeList.attribute_id] = list(attrs.keys())
190+
191+
endpoint_tlv[Clusters.Descriptor.id] = attrs
192+
return endpoint_tlv
193+
194+
def add_macl(self, root_endpoint: dict[int, dict[int, Any]]):
195+
ac = Clusters.AccessControl
196+
root_endpoint[ac.id][ac.Attributes.FeatureMap.attribute_id] = ac.Bitmaps.Feature.kManagedDevice
197+
root_endpoint[ac.id][ac.Attributes.Arl.attribute_id] = []
198+
root_endpoint[ac.id][ac.Attributes.CommissioningARL.attribute_id] = []
199+
root_endpoint[ac.id][ac.Attributes.AttributeList.attribute_id].extend([
200+
ac.Attributes.Arl.attribute_id, ac.Attributes.CommissioningARL.attribute_id])
201+
root_endpoint[ac.id][ac.Attributes.AcceptedCommandList.attribute_id].append(ac.Commands.ReviewFabricRestrictions.command_id)
202+
root_endpoint[ac.id][ac.Attributes.GeneratedCommandList.attribute_id].append(
203+
ac.Commands.ReviewFabricRestrictionsResponse.command_id)
204+
205+
@async_test_body
206+
async def test_macl_handling(self):
207+
nim_id = self._get_device_type_id('network infrastructure manager')
208+
root_node_id = self._get_device_type_id('root node')
209+
on_off_id = self._get_device_type_id('On/Off Light')
210+
211+
root = self._create_minimal_dt(device_type_id=root_node_id)
212+
nim = self._create_minimal_dt(device_type_id=nim_id)
213+
self.endpoints_tlv = {0: root, 1: nim}
214+
asserts.assert_true(self._has_device_type_supporting_macl(), "Did not find supported device in generated device")
215+
216+
success, problems = self.check_conformance(ignore_in_progress=False, is_ci=False, allow_provisional=True)
217+
self.problems.extend(problems)
218+
asserts.assert_true(success, "Unexpected failure parsing minimal dt")
219+
220+
self.add_macl(root)
221+
# A MACL is allowed when there is a NIM, so this should succeed as well
222+
success, problems = self.check_conformance(ignore_in_progress=False, is_ci=False, allow_provisional=True)
223+
self.problems.extend(problems)
224+
asserts.assert_true(success, "Unexpected failure with NIM and MACL")
225+
226+
# A MACL is not allowed when there is no NIM
227+
self.endpoints_tlv[1] = self._create_minimal_dt(device_type_id=on_off_id)
228+
success, problems = self.check_conformance(ignore_in_progress=False, is_ci=False, allow_provisional=True)
229+
self.problems.extend(problems)
230+
asserts.assert_false(success, "Unexpected success with On/Off and MACL")
231+
232+
# TODO: what happens if there is a NIM and a non-NIM endpoint?
233+
138234

139235
if __name__ == "__main__":
140236
default_matter_test_main()

src/python_testing/execute_python_tests.py

+1
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ def main(search_directory, env_file):
8585
"TestChoiceConformanceSupport.py",
8686
"TC_DEMTestBase.py",
8787
"choice_conformance_support.py",
88+
"TestConformanceTest.py", # Unit test of the conformance test (TC_DeviceConformance) - does not run against an app.
8889
"TestIdChecks.py",
8990
"TestSpecParsingDeviceType.py",
9091
"TestConformanceTest.py",

0 commit comments

Comments
 (0)