diff --git a/examples/network-manager-app/linux/tbrm.cpp b/examples/network-manager-app/linux/tbrm.cpp index 908666ee61d8a0..eada060526add7 100644 --- a/examples/network-manager-app/linux/tbrm.cpp +++ b/examples/network-manager-app/linux/tbrm.cpp @@ -94,15 +94,23 @@ class FakeBorderRouterDelegate final : public ThreadBorderRouterManagement::Dele mActivateDatasetCallback = callback; mActivateDatasetSequence = sequenceNum; - DeviceLayer::SystemLayer().StartTimer(System::Clock::Milliseconds32(3000), CompleteDatasetActivation, this); + DeviceLayer::SystemLayer().StartTimer(System::Clock::Milliseconds32(1000), ActivateActiveDataset, this); } CHIP_ERROR CommitActiveDataset() override { return CHIP_NO_ERROR; } CHIP_ERROR RevertActiveDataset() override { return CHIP_ERROR_NOT_IMPLEMENTED; } - CHIP_ERROR SetPendingDataset(const Thread::OperationalDataset & pendingDataset) override { return CHIP_ERROR_NOT_IMPLEMENTED; } + + CHIP_ERROR SetPendingDataset(const Thread::OperationalDataset & pendingDataset) override + { + ReturnErrorOnFailure(mPendingDataset.Init(pendingDataset.AsByteSpan())); + uint32_t delayTimerMillis; + ReturnErrorOnFailure(mPendingDataset.GetDelayTimer(delayTimerMillis)); + DeviceLayer::SystemLayer().StartTimer(System::Clock::Milliseconds32(delayTimerMillis), ActivatePendingDataset, this); + return CHIP_NO_ERROR; + } private: - static void CompleteDatasetActivation(System::Layer *, void * context) + static void ActivateActiveDataset(System::Layer *, void * context) { auto * self = static_cast(context); auto * callback = self->mActivateDatasetCallback; @@ -111,6 +119,18 @@ class FakeBorderRouterDelegate final : public ThreadBorderRouterManagement::Dele callback->OnActivateDatasetComplete(sequenceNum, CHIP_NO_ERROR); } + static void ActivatePendingDataset(System::Layer *, void * context) + { + auto * self = static_cast(context); + self->mActiveDataset.Init(self->mPendingDataset.AsByteSpan()); + self->mPendingDataset.Clear(); + // This could just call MatterReportingAttributeChangeCallback directly + self->mAttributeChangeCallback->ReportAttributeChanged( + ThreadBorderRouterManagement::Attributes::ActiveDatasetTimestamp::Id); + self->mAttributeChangeCallback->ReportAttributeChanged( + ThreadBorderRouterManagement::Attributes::PendingDatasetTimestamp::Id); + } + AttributeChangeCallback * mAttributeChangeCallback; Thread::OperationalDataset mActiveDataset; Thread::OperationalDataset mPendingDataset; diff --git a/examples/network-manager-app/network-manager-common/network-manager-app.matter b/examples/network-manager-app/network-manager-common/network-manager-app.matter index 3f24e4be5744c6..627838288db0d1 100644 --- a/examples/network-manager-app/network-manager-common/network-manager-app.matter +++ b/examples/network-manager-app/network-manager-common/network-manager-app.matter @@ -1892,6 +1892,7 @@ endpoint 1 { handle command GetPendingDatasetRequest; handle command DatasetResponse; handle command SetActiveDatasetRequest; + handle command SetPendingDatasetRequest; } server cluster ThreadNetworkDirectory { diff --git a/examples/network-manager-app/network-manager-common/network-manager-app.zap b/examples/network-manager-app/network-manager-common/network-manager-app.zap index 7c1445ac46c190..1d27e3c346f320 100644 --- a/examples/network-manager-app/network-manager-common/network-manager-app.zap +++ b/examples/network-manager-app/network-manager-common/network-manager-app.zap @@ -3407,6 +3407,14 @@ "source": "client", "isIncoming": 1, "isEnabled": 1 + }, + { + "name": "SetPendingDatasetRequest", + "code": 4, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 } ], "attributes": [ @@ -3500,7 +3508,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, diff --git a/src/app/tests/suites/certification/PICS.yaml b/src/app/tests/suites/certification/PICS.yaml index 9d8011ceee2172..917429f466792a 100644 --- a/src/app/tests/suites/certification/PICS.yaml +++ b/src/app/tests/suites/certification/PICS.yaml @@ -10298,6 +10298,17 @@ PICS: - label: "Does the device implement the ActiveEndpoints attribute?" id: PWRTL.S.A0001 + # + # Thread Border Router Management Cluster + # + - label: + "Does the device implement the Thread Border Router Management cluster + as a server?" + id: TBRM.S + + - label: "Does the device support the PanChange feature?" + id: TBRM.S.F00 + # # Thread Network Directory Cluster # diff --git a/src/app/tests/suites/certification/Test_TC_TBRM_2_2.yaml b/src/app/tests/suites/certification/Test_TC_TBRM_2_2.yaml new file mode 100644 index 00000000000000..852e18a73adc8a --- /dev/null +++ b/src/app/tests/suites/certification/Test_TC_TBRM_2_2.yaml @@ -0,0 +1,116 @@ +# 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-TBRM-2.2] Initial Dataset configuration of Thread Border Router" + +PICS: + - TBRM.S + +config: + nodeId: 0x12344321 + cluster: Thread Border Router Management + endpoint: 1 + PIXIT.TBRM.THREAD_ACTIVE_DATASET: + type: octet_string + defaultValue: "hex:0e080000000000010000000300001235060004001fffe002082ad51c02fe8f64f20708fddb8af85255f93a051083e2b9b2cc609b00125adbf823ea2ab20102c4d904100a133626c411d7de02a570ca3c3d80470c0402a0f7f8031054687265616441637469766554657374" + # Active Timestamp ----^^^^^^^^^^^^^^^^ + PIXIT.TBRM.THREAD_ACTIVE_DATASET.ACTIVE_TIMESTAMP: 0x10000 + PIXIT.TBRM.THREAD_INVALID_DATASET: + type: octet_string + defaultValue: "hex:00112233" + +# Precondition: +# The DUT does not have an Active Dataset + +tests: + - label: "Wait for the commissioned device to be retrieved" + cluster: DelayCommands + command: WaitForCommissionee + arguments: + values: + - name: nodeId + value: nodeId + + - label: "TH reads the ActiveDatasetTimestamp attribute from the DUT" + command: readAttribute + attribute: ActiveDatasetTimestamp + response: + value: null + + - label: "TH reads the PendingDatasetTimestamp attribute from the DUT" + command: readAttribute + attribute: PendingDatasetTimestamp + response: + value: null + + - label: + "TH sends a valid ActiveDatasetRequest command to the DUT without + having armed the fail-safe" + command: SetActiveDatasetRequest + arguments: + values: + - name: ActiveDataset + value: PIXIT.TBRM.THREAD_ACTIVE_DATASET + response: + error: FAILSAFE_REQUIRED + + - label: "TH sends ArmFailSafe command to the DUT" + cluster: General Commissioning + command: ArmFailSafe + endpoint: 0 + arguments: + values: + - name: ExpiryLengthSeconds + value: 60 + - name: Breadcrumb + value: 1 + + - label: "TH sends an invalid ActiveDatasetRequest command to the DUT" + command: SetActiveDatasetRequest + arguments: + values: + - name: ActiveDataset + value: PIXIT.TBRM.THREAD_INVALID_DATASET + response: + error: INVALID_COMMAND + + - label: "TH sends a valid ActiveDatasetRequest command to the DUT" + command: SetActiveDatasetRequest + arguments: + values: + - name: ActiveDataset + value: PIXIT.TBRM.THREAD_ACTIVE_DATASET + + - label: "TH reads the InterfaceEnabled attribute from the DUT" + command: readAttribute + attribute: InterfaceEnabled + response: + value: true + + - label: "TH reads the ActiveDatasetTimestamp attribute from the DUT" + command: readAttribute + attribute: ActiveDatasetTimestamp + response: + value: PIXIT.TBRM.THREAD_ACTIVE_DATASET.ACTIVE_TIMESTAMP + constraints: + type: int64u + + - label: "TH sends a valid GetActiveDatasetRequest command to the DUT" + command: GetActiveDatasetRequest + response: + values: + - name: Dataset + value: PIXIT.TBRM.THREAD_ACTIVE_DATASET + constraints: + type: octet_string diff --git a/src/app/tests/suites/certification/Test_TC_TBRM_2_3.yaml b/src/app/tests/suites/certification/Test_TC_TBRM_2_3.yaml new file mode 100644 index 00000000000000..7a55191f471f0b --- /dev/null +++ b/src/app/tests/suites/certification/Test_TC_TBRM_2_3.yaml @@ -0,0 +1,166 @@ +# 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-TBRM-2.3] Change dataset configuration of Thread Border Router" + +PICS: + - TBRM.S + - TBRM.S.F00 + +config: + nodeId: 0x12344321 + cluster: Thread Border Router Management + endpoint: 1 + PIXIT.TBRM.THREAD_ACTIVE_DATASET: + type: octet_string + defaultValue: "hex:0e080000000000010000000300001235060004001fffe002082ad51c02fe8f64f20708fddb8af85255f93a051083e2b9b2cc609b00125adbf823ea2ab20102c4d904100a133626c411d7de02a570ca3c3d80470c0402a0f7f8031054687265616441637469766554657374" + PIXIT.TBRM.THREAD_PENDING_DATASET: + type: octet_string + defaultValue: "hex:0e08000000000002000033080000000000010000340400004e2035060004001fffe002082ad51c02fe8f64f20708fddb8af85255f93a051083e2b9b2cc609b00125adbf823ea2ab20102c4d904100a133626c411d7de02a570ca3c3d80470c0402a0f7f8030d54687265616450656e64696e67000300000c" + # Active Timestamp ----^^^^^^^^^^^^^^^^ + # Pending Timestamp -----------------------^^^^^^^^^^^^^^^^ + # Delay Timer -------------------------------------------------^^^^^^^^ == 20000ms Note: waitForReport has a hard-coded 30 second timeout + +tests: + - label: "Wait for the commissioned device to be retrieved" + cluster: DelayCommands + command: WaitForCommissionee + arguments: + values: + - name: nodeId + value: nodeId + # Step 1 + - label: "TH reads the ActiveDatasetTimestamp attribute from the DUT" + command: readAttribute + attribute: ActiveDatasetTimestamp + response: + saveAs: initialActiveTimestamp + constraints: + type: int64u + + - label: "If the ActiveDatasetTimestamp attribute not null, go to step 4" + cluster: EqualityCommands + command: UnsignedNumberEquals + arguments: + values: + - name: Value1 + value: initialActiveTimestamp + - name: Value2 + value: null + response: + - values: + - name: Equals + saveAs: noActiveDataset + + # Step 2 + - label: "TH sends ArmFailSafe command to the DUT" + runIf: noActiveDataset + cluster: General Commissioning + command: ArmFailSafe + endpoint: 0 + arguments: + values: + - name: ExpiryLengthSeconds + value: 60 + - name: Breadcrumb + value: 1 + + # Step 3 + - label: "TH sends a valid ActiveDatasetRequest command to the DUT" + runIf: noActiveDataset + command: SetActiveDatasetRequest + arguments: + values: + - name: ActiveDataset + value: PIXIT.TBRM.THREAD_ACTIVE_DATASET + + # Step 4 + - label: "TH reads the PendingDatasetTimestamp attribute from the DUT" + command: readAttribute + attribute: PendingDatasetTimestamp + response: + saveAs: initialPendingTimestamp + constraints: + type: int64u + + # Step 5 + - label: "TH sends a SetPendingDatasetRequest command to the DUT" + command: SetPendingDatasetRequest + arguments: + values: + - name: PendingDataset + value: PIXIT.TBRM.THREAD_PENDING_DATASET + + # Step 6 + - label: "TH sends a GetPendingDatasetRequest command to the DUT" + command: GetPendingDatasetRequest + response: + values: + - name: Dataset + constraints: + type: octet_string + # TODO: This should be PIXIT.TBRM.THREAD_PENDING_DATASET but ignoring the Delay Timer element if present + + # Step 7 + - label: "TH reads the PendingDatasetTimestamp attribute from the DUT" + command: readAttribute + attribute: PendingDatasetTimestamp + response: + constraints: + type: int64u + notValue: initialPendingTimestamp + + # Step 8 + - label: + "TH subscribes to the ActiveDatasetTimestamp attribute from the DUT" + command: subscribeAttribute + attribute: ActiveDatasetTimestamp + minInterval: 1 + maxInterval: 70 + response: + constraints: + type: int64u + hasValue: true # not null + + - label: "TH waits for an ActiveDatasetTimestamp report" + command: waitForReport + attribute: ActiveDatasetTimestamp + # TODO: waitForReport uses a hard-coded timeout of 30s, should be configurable? + response: + constraints: + hasValue: true + + # Step 9 + - label: "TH reads the PendingDatasetTimestamp attribute from the DUT" + command: readAttribute + attribute: PendingDatasetTimestamp + response: + value: null + + # Step 10 + - label: "TH sends a valid GetActiveDatasetRequest command to the DUT" + command: GetActiveDatasetRequest + response: + values: + - name: Dataset + # TODO: This should be PIXIT.TBRM.THREAD_PENDING_DATASET without the Delay Timer and Pending Timestamp elements + constraints: + type: octet_string + + # Step 11 + - label: "TH reads the InterfaceEnabled attribute from the DUT" + command: readAttribute + attribute: InterfaceEnabled + response: + value: true diff --git a/src/app/tests/suites/certification/ci-pics-values b/src/app/tests/suites/certification/ci-pics-values index 70a0e79760be3a..bedc062d553b25 100644 --- a/src/app/tests/suites/certification/ci-pics-values +++ b/src/app/tests/suites/certification/ci-pics-values @@ -3017,6 +3017,10 @@ PWRTL.S.F01=0 PWRTL.S.F02=1 PWRTL.S.F03=1 +# Thread Border Router Management Cluster +TBRM.S=1 +TBRM.S.F00=1 + # Thread Network Directory Cluster THNETDIR.S=1 diff --git a/src/lib/support/ThreadOperationalDataset.cpp b/src/lib/support/ThreadOperationalDataset.cpp index 1c11712166cfa4..39ba6b3b33b196 100644 --- a/src/lib/support/ThreadOperationalDataset.cpp +++ b/src/lib/support/ThreadOperationalDataset.cpp @@ -55,6 +55,7 @@ class ThreadTLV final kMeshLocalPrefix = 7, kSecurityPolicy = 12, kActiveTimestamp = 14, + kDelayTimer = 52, kChannelMask = 53, }; @@ -426,6 +427,24 @@ CHIP_ERROR OperationalDataset::SetSecurityPolicy(uint32_t aSecurityPolicy) return CHIP_NO_ERROR; } +CHIP_ERROR OperationalDataset::GetDelayTimer(uint32_t & aDelayMillis) const +{ + const ThreadTLV * tlv = Locate(ThreadTLV::kDelayTimer); + VerifyOrReturnError(tlv != nullptr, CHIP_ERROR_TLV_TAG_NOT_FOUND); + VerifyOrReturnError(tlv->GetLength() == sizeof(aDelayMillis), CHIP_ERROR_INVALID_TLV_ELEMENT); + tlv->Get32(aDelayMillis); + return CHIP_NO_ERROR; +} + +CHIP_ERROR OperationalDataset::SetDelayTimer(uint32_t aDelayMillis) +{ + ThreadTLV * tlv = MakeRoom(ThreadTLV::kDelayTimer, sizeof(*tlv) + sizeof(aDelayMillis)); + VerifyOrReturnError(tlv != nullptr, CHIP_ERROR_NO_MEMORY); + tlv->Set32(aDelayMillis); + mLength = static_cast(mLength + tlv->GetSize()); + return CHIP_NO_ERROR; +} + void OperationalDataset::UnsetMasterKey() { Remove(ThreadTLV::kMasterKey); diff --git a/src/lib/support/ThreadOperationalDataset.h b/src/lib/support/ThreadOperationalDataset.h index 5cd57df6a79db6..a19d9827bbd610 100644 --- a/src/lib/support/ThreadOperationalDataset.h +++ b/src/lib/support/ThreadOperationalDataset.h @@ -294,6 +294,23 @@ class OperationalDataset */ CHIP_ERROR SetSecurityPolicy(uint32_t aSecurityPolicy); + /** + * Retrieves the delay timer from the dataset. + * + * @retval CHIP_NO_ERROR on success. + * @retval CHIP_ERROR_TLV_TAG_NOT_FOUND if no security policy is present in the dataset. + * @retval CHIP_ERROR_INVALID_TLV_ELEMENT if the TLV element is invalid. + */ + CHIP_ERROR GetDelayTimer(uint32_t & aDelayMillis) const; + + /** + * This method sets the delay timer within the dataset. + * + * @retval CHIP_NO_ERROR on success. + * @retval CHIP_ERROR_NO_MEMORY if there is insufficient space within the dataset. + */ + CHIP_ERROR SetDelayTimer(uint32_t aDelayMillis); + /** * This method clears all data stored in the dataset. */