diff --git a/examples/chip-tool/py_matter_chip_tool_adapter/matter_chip_tool_adapter/encoder.py b/examples/chip-tool/py_matter_chip_tool_adapter/matter_chip_tool_adapter/encoder.py index f4e8e6711d7105..252d23c0375836 100644 --- a/examples/chip-tool/py_matter_chip_tool_adapter/matter_chip_tool_adapter/encoder.py +++ b/examples/chip-tool/py_matter_chip_tool_adapter/matter_chip_tool_adapter/encoder.py @@ -130,6 +130,14 @@ 'has_destination': False, 'has_endpoint': False, }, + 'EstablishPASESession': { + 'alias': 'code-paseonly', + 'arguments': { + 'nodeId': 'node-id' + }, + 'has_destination': False, + 'has_endpoint': False, + }, 'GetCommissionerNodeId': { 'has_destination': False, 'has_endpoint': False, diff --git a/scripts/py_matter_yamltests/matter_yamltests/pseudo_clusters/clusters/commissioner_commands.py b/scripts/py_matter_yamltests/matter_yamltests/pseudo_clusters/clusters/commissioner_commands.py index d3cc92b7aa10b9..451d1ee188ff88 100644 --- a/scripts/py_matter_yamltests/matter_yamltests/pseudo_clusters/clusters/commissioner_commands.py +++ b/scripts/py_matter_yamltests/matter_yamltests/pseudo_clusters/clusters/commissioner_commands.py @@ -57,6 +57,11 @@ + + + + + ''' diff --git a/scripts/tests/chiptest/__init__.py b/scripts/tests/chiptest/__init__.py index 03ac663427ce84..2826e506382556 100644 --- a/scripts/tests/chiptest/__init__.py +++ b/scripts/tests/chiptest/__init__.py @@ -220,6 +220,7 @@ def _GetDarwinFrameworkToolUnsupportedTests() -> Set[str]: "Test_TC_SC_4_1", # darwin-framework-tool does not support dns-sd commands. "Test_TC_SC_5_2", # darwin-framework-tool does not support group commands. "Test_TC_S_2_3", # darwin-framework-tool does not support group commands. + "Test_TC_THNETDIR_2_2", # darwin-framework-tool does not support negative timed-invoke tests } diff --git a/src/app/clusters/thread-network-directory-server/thread-network-directory-server.cpp b/src/app/clusters/thread-network-directory-server/thread-network-directory-server.cpp index 64aa0a583738c6..5718c39e6f719a 100644 --- a/src/app/clusters/thread-network-directory-server/thread-network-directory-server.cpp +++ b/src/app/clusters/thread-network-directory-server/thread-network-directory-server.cpp @@ -182,9 +182,9 @@ void ThreadNetworkDirectoryServer::HandleAddNetworkRequest(HandlerContext & ctx, { OperationalDataset dataset; ByteSpan extendedPanIdSpan; + uint64_t activeTimestamp; union { - uint64_t activeTimestamp; uint16_t channel; uint8_t masterKey[kSizeMasterKey]; uint8_t meshLocalPrefix[kSizeMeshLocalPrefix]; @@ -203,7 +203,7 @@ void ThreadNetworkDirectoryServer::HandleAddNetworkRequest(HandlerContext & ctx, // TODO: An immutable OperationalDatasetView on top of a ByteSpan (without copying) would be useful here. SuccessOrExitAction(err = dataset.Init(req.operationalDataset), context = "OperationalDataset"); SuccessOrExitAction(err = dataset.GetExtendedPanIdAsByteSpan(extendedPanIdSpan), context = "ExtendedPanID"); - SuccessOrExitAction(err = dataset.GetActiveTimestamp(unused.activeTimestamp), context = "ActiveTimestamp"); + SuccessOrExitAction(err = dataset.GetActiveTimestamp(activeTimestamp), context = "ActiveTimestamp"); SuccessOrExitAction(err = dataset.GetChannel(unused.channel), context = "Channel"); SuccessOrExitAction(err = dataset.GetChannelMask(unusedSpan), context = "ChannelMask"); SuccessOrExitAction(err = dataset.GetMasterKey(unused.masterKey), context = "NetworkKey"); @@ -214,6 +214,27 @@ void ThreadNetworkDirectoryServer::HandleAddNetworkRequest(HandlerContext & ctx, SuccessOrExitAction(err = dataset.GetSecurityPolicy(unused.securityPolicy), context = "SecurityContext"); status = IMStatus::Failure; + + // "If the received dataset has an Active Timestamp that is less than or equal to that of the existing entry, + // then the update SHALL be rejected with a status of INVALID_IN_STATE." + { + uint8_t datasetBuffer[kSizeOperationalDataset]; + MutableByteSpan datasetSpan(datasetBuffer); + err = mStorage.GetNetworkDataset(ExtendedPanId(extendedPanIdSpan), datasetSpan); + if (err != CHIP_ERROR_NOT_FOUND) + { + SuccessOrExit(err); + SuccessOrExit(err = dataset.Init(datasetSpan)); + uint64_t existingActiveTimestamp; + SuccessOrExit(err = dataset.GetActiveTimestamp(existingActiveTimestamp)); + if (activeTimestamp <= existingActiveTimestamp) + { + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, IMStatus::InvalidInState, "ActiveTimestamp"); + return; + } + } + } + SuccessOrExit(err = mStorage.AddOrUpdateNetwork(ExtendedPanId(extendedPanIdSpan), req.operationalDataset)); ctx.mCommandHandler.AddStatus(ctx.mRequestPath, IMStatus::Success); @@ -266,6 +287,12 @@ void ThreadNetworkDirectoryServer::HandleOperationalDatasetRequest( { CHIP_ERROR err; + if (ctx.mCommandHandler.GetSubjectDescriptor().authMode != Access::AuthMode::kCase) + { + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, IMStatus::UnsupportedAccess); + return; + } + if (req.extendedPanID.size() != ExtendedPanId::size()) { ctx.mCommandHandler.AddStatus(ctx.mRequestPath, IMStatus::ConstraintError); diff --git a/src/app/clusters/wifi-network-management-server/wifi-network-management-server.cpp b/src/app/clusters/wifi-network-management-server/wifi-network-management-server.cpp index 79aac105e4e3fe..2f20ff36a09536 100644 --- a/src/app/clusters/wifi-network-management-server/wifi-network-management-server.cpp +++ b/src/app/clusters/wifi-network-management-server/wifi-network-management-server.cpp @@ -32,7 +32,7 @@ using namespace chip::app; using namespace chip::app::Clusters; using namespace chip::app::Clusters::WiFiNetworkManagement::Attributes; using namespace chip::app::Clusters::WiFiNetworkManagement::Commands; -using namespace std::placeholders; +using IMStatus = chip::Protocols::InteractionModel::Status; namespace chip { namespace app { @@ -143,6 +143,12 @@ void WiFiNetworkManagementServer::InvokeCommand(HandlerContext & ctx) void WiFiNetworkManagementServer::HandleNetworkPassphraseRequest(HandlerContext & ctx, const NetworkPassphraseRequest::DecodableType & req) { + if (ctx.mCommandHandler.GetSubjectDescriptor().authMode != Access::AuthMode::kCase) + { + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, IMStatus::UnsupportedAccess); + return; + } + if (HaveNetworkCredentials()) { NetworkPassphraseResponse::Type response; @@ -151,8 +157,7 @@ void WiFiNetworkManagementServer::HandleNetworkPassphraseRequest(HandlerContext } else { - // TODO: Status code TBC: https://github.com/CHIP-Specifications/connectedhomeip-spec/issues/9234 - ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Protocols::InteractionModel::Status::InvalidInState); + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, IMStatus::InvalidInState); } } diff --git a/src/app/tests/suites/certification/Test_TC_THNETDIR_2_1.yaml b/src/app/tests/suites/certification/Test_TC_THNETDIR_2_1.yaml index 3a2925dc424175..fc5129295adf84 100644 --- a/src/app/tests/suites/certification/Test_TC_THNETDIR_2_1.yaml +++ b/src/app/tests/suites/certification/Test_TC_THNETDIR_2_1.yaml @@ -47,5 +47,4 @@ tests: response: constraints: type: int8u - minValue: 10 # assume 5 supported fabrics - # python: value >= 2 * supportedFabrics + python: value >= 2 * supportedFabrics diff --git a/src/app/tests/suites/certification/Test_TC_THNETDIR_2_2.yaml b/src/app/tests/suites/certification/Test_TC_THNETDIR_2_2.yaml index be0ec89952d25e..a485a302f2127b 100644 --- a/src/app/tests/suites/certification/Test_TC_THNETDIR_2_2.yaml +++ b/src/app/tests/suites/certification/Test_TC_THNETDIR_2_2.yaml @@ -25,13 +25,16 @@ config: # Note: TestNetwork* values need to match what's encoded in TestNetworkDataset TestNetworkDataset: type: octet_string - defaultValue: "hex:0e080000000000000001000300000f350407fff800020839758ec8144b07fb0708fdf1f1add0797dc00510f366cec7a446bab978d90d27abe38f23030f4f70656e5468726561642d353933380102593804103ca67c969efb0d0c74a4d8ee923b576c0c0402a0f7f8" + defaultValue: "hex:0e0800000000000c0001000300000f350407fff800020839758ec8144b07fb0708fdf1f1add0797dc00510f366cec7a446bab978d90d27abe38f23030f4f70656e5468726561642d353933380102593804103ca67c969efb0d0c74a4d8ee923b576c0c0402a0f7f8" TestNetworkExtendedPanId: type: octet_string defaultValue: "hex:39758ec8144b07fb" TestNetworkName: "OpenThread-5938" TestNetworkChannel: 15 - TestNetworkActiveTimestamp: 1 + TestNetworkActiveTimestamp: 0xc0001 + TestNetworkUpdatedDataset: + type: octet_string + defaultValue: "hex:0e0800000000000d0001000300000f350407fff800020839758ec8144b07fb0708fdf1f1add0797dc00510f366cec7a446bab978d90d27abe38f23030f4f70656e5468726561642d353933380102593804103ca67c969efb0d0c74a4d8ee923b576c0c0402a0f7f8" tests: - label: "Wait for the commissioned device to be retrieved" @@ -90,15 +93,15 @@ tests: response: error: NOT_FOUND - # TODO: Currently fails with darwin-framework-tool because it automatically performs a timed invoke - # - label: "TH sends AddNetwork command to DUT without a timed interaction" - # command: AddNetwork - # arguments: - # values: - # - name: OperationalDataset - # value: TestNetworkDataset - # response: - # error: NEEDS_TIMED_INTERACTION + # Note: Unsupported with darwin-framework-tool because it automatically performs a timed invoke + - label: "TH sends AddNetwork command to DUT without a timed interaction" + command: AddNetwork + arguments: + values: + - name: OperationalDataset + value: TestNetworkDataset + response: + error: NEEDS_TIMED_INTERACTION - label: "TH sends AddNetwork command to DUT with TestNetwork dataset" command: AddNetwork @@ -116,18 +119,43 @@ tests: response: constraints: type: list - # python: | - # # Split the list into test (our TestNetwork) and rest (everything else) - # test = next((n for n in value if n['ExtendedPanID'] == TestNetworkExtendedPanId), None) - # rest = [n for n in value if n != test] - # # Check test has the expected values and rest == initialNetworks (ignoring order) - # return (test is not None and - # test['NetworkName'] == TestNetworkName and - # test['Channel'] == TestNetworkChannel and - # test['ActiveTimestamp'] == TestNetworkActiveTimestamp and - # len(value) == len(initialNetworks) + 1 and - # len(rest) == len(initialNetworks) and - # all(n in initialNetworks for n in rest)) + python: | + # Split the list into test (our TestNetwork) and rest (everything else) + test = next((n for n in value if n['ExtendedPanID'] == TestNetworkExtendedPanId), None) + rest = [n for n in value if n != test] + # Check test has the expected values and rest == initialNetworks (ignoring order) + return (test is not None and + test['NetworkName'] == TestNetworkName and + test['Channel'] == TestNetworkChannel and + test['ActiveTimestamp'] == TestNetworkActiveTimestamp and + len(value) == len(initialNetworks) + 1 and + len(rest) == len(initialNetworks) and + all(n in initialNetworks for n in rest)) + + - label: + "TH sends GetOperationalDataset command to DUT with ExtendedPanID from + TestNetwork" + command: GetOperationalDataset + arguments: + values: + - name: ExtendedPanID + value: TestNetworkExtendedPanId + response: + values: + - name: OperationalDataset + value: TestNetworkDataset + + - label: + "TH sends AddNetwork command to DUT with TestNetwork dataset matching + existing Active Timestamp" + command: AddNetwork + timedInteractionTimeoutMs: 2000 + arguments: + values: + - name: OperationalDataset + value: TestNetworkDataset + response: + error: INVALID_IN_STATE - label: "TH sends GetOperationalDataset command to DUT with ExtendedPanID from @@ -142,6 +170,53 @@ tests: - name: OperationalDataset value: TestNetworkDataset + - label: + "TH sends AddNetwork command to DUT with updated TestNetwork dataset + with larger Active Timestamp" + command: AddNetwork + timedInteractionTimeoutMs: 2000 + arguments: + values: + - name: OperationalDataset + value: TestNetworkUpdatedDataset + + - label: + "TH sends GetOperationalDataset command to DUT with ExtendedPanID from + TestNetwork" + command: GetOperationalDataset + arguments: + values: + - name: ExtendedPanID + value: TestNetworkExtendedPanId + response: + values: + - name: OperationalDataset + value: TestNetworkUpdatedDataset + + - label: + "TH sends AddNetwork command to DUT with original TestNetwork dataset" + command: AddNetwork + timedInteractionTimeoutMs: 2000 + arguments: + values: + - name: OperationalDataset + value: TestNetworkDataset + response: + error: INVALID_IN_STATE + + - label: + "TH sends GetOperationalDataset command to DUT with ExtendedPanID from + TestNetwork" + command: GetOperationalDataset + arguments: + values: + - name: ExtendedPanID + value: TestNetworkExtendedPanId + response: + values: + - name: OperationalDataset + value: TestNetworkUpdatedDataset + - label: "TH writes ExtendedPanID from TestNetwork to PreferredExtendedPanID on DUT" @@ -158,15 +233,15 @@ tests: response: value: TestNetworkExtendedPanId - # TODO: Currently fails with darwin-framework-tool because it automatically performs a timed invoke - # - label: "TH sends RemoveNetwork command to DUT without a timed interaction" - # command: RemoveNetwork - # arguments: - # values: - # - name: ExtendedPanID - # value: TestNetworkExtendedPanId - # response: - # error: NEEDS_TIMED_INTERACTION + # Note: Unsupported with darwin-framework-tool because it automatically performs a timed invoke + - label: "TH sends RemoveNetwork command to DUT without a timed interaction" + command: RemoveNetwork + arguments: + values: + - name: ExtendedPanID + value: TestNetworkExtendedPanId + response: + error: NEEDS_TIMED_INTERACTION - label: "TH sends RemoveNetwork command to DUT with ExtendedPanID of @@ -212,10 +287,10 @@ tests: response: constraints: type: list - # python: | - # # value == initialNetworks (ignoring order) - # return (len(value) == len(initialNetworks) and - # all(n in initialNetworks for n in value)) + python: | + # value == initialNetworks (ignoring order) + return (len(value) == len(initialNetworks) and + all(n in initialNetworks for n in value)) - label: "TH writes PreferredExtendedPanID to DUT, restoring the initial value" diff --git a/src/app/tests/suites/certification/Test_TC_THNETDIR_2_3.yaml b/src/app/tests/suites/certification/Test_TC_THNETDIR_2_3.yaml new file mode 100644 index 00000000000000..f0b5b9bf2288ba --- /dev/null +++ b/src/app/tests/suites/certification/Test_TC_THNETDIR_2_3.yaml @@ -0,0 +1,85 @@ +# Copyright (c) 2024 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. + +name: + "[TC-THNETDIR-2.3] Verify CASE session requirement for GetOperationalDataset" + +PICS: + - THNETDIR.S + +config: + nodeId: 0x12344321 + cluster: Thread Network Directory + endpoint: 1 + + TestNetworkExtendedPanId: + type: octet_string + defaultValue: "hex:39758ec8144b07fb" + + payload: "MT:-24J0AFN00KA0648G00" + discriminator: 3840 + PakeVerifier: + type: octet_string + defaultValue: "hex:b96170aae803346884724fe9a3b287c30330c2a660375d17bb205a8cf1aecb350457f8ab79ee253ab6a8e46bb09e543ae422736de501e3db37d441fe344920d09548e4c18240630c4ff4913c53513839b7c07fcc0627a1b8573a149fcd1fa466cf" + +tests: + - label: "Wait for the commissioned device to be retrieved" + cluster: DelayCommands + command: WaitForCommissionee + arguments: + values: + - name: nodeId + value: nodeId + + - label: "Open Commissioning Window" + endpoint: 0 + cluster: Administrator Commissioning + command: OpenCommissioningWindow + timedInteractionTimeoutMs: 2000 + arguments: + values: + - name: CommissioningTimeout + value: 180 + - name: PAKEPasscodeVerifier + value: PakeVerifier + - name: Discriminator + value: discriminator + - name: Iterations + value: 1000 + - name: Salt + value: "SPAKE2P Key Salt" + + - label: "TH2 establishes a PASE session with the DUT" + identity: beta + endpoint: 0 + cluster: CommissionerCommands + command: EstablishPASESession + arguments: + values: + - name: nodeId + value: nodeId + - name: payload + value: payload + + - label: + "TH2 sends GetOperationalDataset command to the DUT over the PASE + session" + identity: beta + command: GetOperationalDataset + arguments: + values: + - name: ExtendedPanID + value: TestNetworkExtendedPanId + response: + error: UNSUPPORTED_ACCESS diff --git a/src/app/tests/suites/certification/Test_TC_WIFINM_2_2.yaml b/src/app/tests/suites/certification/Test_TC_WIFINM_2_2.yaml new file mode 100644 index 00000000000000..3aaa04996681c8 --- /dev/null +++ b/src/app/tests/suites/certification/Test_TC_WIFINM_2_2.yaml @@ -0,0 +1,82 @@ +# Copyright (c) 2024 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. + +name: + "[TC-WIFINM-2.2] Verify CASE session requirement for + NetworkPassphraseRequest" + +PICS: + - WIFINM.S + +config: + nodeId: 0x12344321 + cluster: WiFi Network Management + endpoint: 1 + + TestNetworkExtendedPanId: + type: octet_string + defaultValue: "hex:39758ec8144b07fb" + + payload: "MT:-24J0AFN00KA0648G00" + discriminator: 3840 + PakeVerifier: + type: octet_string + defaultValue: "hex:b96170aae803346884724fe9a3b287c30330c2a660375d17bb205a8cf1aecb350457f8ab79ee253ab6a8e46bb09e543ae422736de501e3db37d441fe344920d09548e4c18240630c4ff4913c53513839b7c07fcc0627a1b8573a149fcd1fa466cf" + +tests: + - label: "Wait for the commissioned device to be retrieved" + cluster: DelayCommands + command: WaitForCommissionee + arguments: + values: + - name: nodeId + value: nodeId + + - label: "Open Commissioning Window" + endpoint: 0 + cluster: Administrator Commissioning + command: OpenCommissioningWindow + timedInteractionTimeoutMs: 2000 + arguments: + values: + - name: CommissioningTimeout + value: 180 + - name: PAKEPasscodeVerifier + value: PakeVerifier + - name: Discriminator + value: discriminator + - name: Iterations + value: 1000 + - name: Salt + value: "SPAKE2P Key Salt" + + - label: "TH2 establishes a PASE session with the DUT" + identity: beta + endpoint: 0 + cluster: CommissionerCommands + command: EstablishPASESession + arguments: + values: + - name: nodeId + value: nodeId + - name: payload + value: payload + + - label: + "TH2 sends the NetworkPassphraseRequest command to the DUT over the + PASE session" + identity: beta + command: NetworkPassphraseRequest + response: + error: UNSUPPORTED_ACCESS diff --git a/src/controller/python/chip/yaml/runner.py b/src/controller/python/chip/yaml/runner.py index 8a19109439e3cf..44318f8cbedf89 100644 --- a/src/controller/python/chip/yaml/runner.py +++ b/src/controller/python/chip/yaml/runner.py @@ -650,7 +650,7 @@ def __init__(self, test_step): if test_step.command == 'GetCommissionerNodeId': # Just setting the self._command is enough for run_action below. pass - elif test_step.command == 'PairWithCode': + elif test_step.command in ('PairWithCode', 'EstablishPASESession'): args = test_step.arguments['values'] request_data_as_dict = Converter.convert_list_of_name_value_pair_to_dict(args) self._setup_payload = request_data_as_dict['payload'] @@ -663,7 +663,10 @@ async def run_action(self, dev_ctrl: ChipDeviceController) -> _ActionResult: return _ActionResult(status=_ActionStatus.SUCCESS, response=_GetCommissionerNodeIdResult(dev_ctrl.nodeId)) try: - await dev_ctrl.CommissionWithCode(self._setup_payload, self._node_id) + if self._command == 'PairWithCode': + await dev_ctrl.CommissionWithCode(self._setup_payload, self._node_id) + elif self._command == 'EstablishPASESession': + await dev_ctrl.EstablishPASESession(self._setup_payload, self._node_id) return _ActionResult(status=_ActionStatus.SUCCESS, response=None) except ChipStackError: return _ActionResult(status=_ActionStatus.ERROR, response=None)