From 6d8ec81eebd4334f5216eab01ee43f67ec40a06f Mon Sep 17 00:00:00 2001 From: jamesharrow <93921463+jamesharrow@users.noreply.github.com> Date: Tue, 3 Sep 2024 21:28:33 +0100 Subject: [PATCH 01/15] Add Q Quality test for EVSE (#35339) * Initial stab at Q Quality test for EVSE - but issue with events not being received so commented out for now. * Removed some debug * Fixed issue with events - keepSubscriptions needs to be True on step 4 * Updated TC_EEVSE_2_6 to check session ID does increment on next plug-in (not on unplugged). * Added StopSession() to update values once unplugged. Fixed issue with session reporting which was using (wrong) endpoint 0. Avoided recursive update when getting session duration causing attribute to be marked as dirty. Fix to state machine from real EV testing. * Corrected test script assert. * Restyled by whitespace * Restyled by clang-format * Restyled by isort * Reverted change to state machine which broke EEVSE_2_2 * Updated TC_EEVSE_2.6 based on code review comments - added step 3 for event subscription. * Updated with code review comments. --------- Co-authored-by: Restyled.io --- .../include/EnergyEvseDelegateImpl.h | 28 +- .../src/EnergyEvseDelegateImpl.cpp | 52 +++- src/python_testing/TC_EEVSE_2_6.py | 293 ++++++++++++++++++ 3 files changed, 349 insertions(+), 24 deletions(-) create mode 100644 src/python_testing/TC_EEVSE_2_6.py diff --git a/examples/energy-management-app/energy-management-common/energy-evse/include/EnergyEvseDelegateImpl.h b/examples/energy-management-app/energy-management-common/energy-evse/include/EnergyEvseDelegateImpl.h index c43707d9d09589..0b84e8fcc87c22 100644 --- a/examples/energy-management-app/energy-management-common/energy-evse/include/EnergyEvseDelegateImpl.h +++ b/examples/energy-management-app/energy-management-common/energy-evse/include/EnergyEvseDelegateImpl.h @@ -56,33 +56,47 @@ enum EVSEStateMachineEvent class EvseSession { public: - EvseSession(EndpointId aEndpoint) { mEndpointId = aEndpoint; } + EvseSession() {} /** * @brief This function records the start time and provided energy meter values as part of the new session. * + * @param endpointId - The endpoint to report the update on * @param chargingMeterValue - The current value of the energy meter (charging) in mWh * @param dischargingMeterValue - The current value of the energy meter (discharging) in mWh */ - void StartSession(int64_t chargingMeterValue, int64_t dischargingMeterValue); + void StartSession(EndpointId endpointId, int64_t chargingMeterValue, int64_t dischargingMeterValue); + + /** + * @brief This function updates the session information at the unplugged event + * + * @param endpointId - The endpoint to report the update on + * @param chargingMeterValue - The current value of the energy meter (charging) in mWh + * @param dischargingMeterValue - The current value of the energy meter (discharging) in mWh + */ + void StopSession(EndpointId endpointId, int64_t chargingMeterValue, int64_t dischargingMeterValue); /** * @brief This function updates the session Duration to allow read attributes to return latest values + * + * @param endpointId - The endpoint to report the update on */ - void RecalculateSessionDuration(); + void RecalculateSessionDuration(EndpointId endpointId); /** * @brief This function updates the EnergyCharged meter value * + * @param endpointId - The endpoint to report the update on * @param chargingMeterValue - The value of the energy meter (charging) in mWh */ - void UpdateEnergyCharged(int64_t chargingMeterValue); + void UpdateEnergyCharged(EndpointId endpointId, int64_t chargingMeterValue); /** * @brief This function updates the EnergyDischarged meter value * + * @param endpointId - The endpoint to report the update on * @param dischargingMeterValue - The value of the energy meter (discharging) in mWh */ - void UpdateEnergyDischarged(int64_t dischargingMeterValue); + void UpdateEnergyDischarged(EndpointId endpointId, int64_t dischargingMeterValue); /* Public members - represent attributes in the cluster */ DataModel::Nullable mSessionID; @@ -91,8 +105,6 @@ class EvseSession DataModel::Nullable mSessionEnergyDischarged; private: - EndpointId mEndpointId = 0; - uint32_t mStartTime = 0; // Epoch_s - 0 means it hasn't started yet int64_t mSessionEnergyChargedAtStart = 0; // in mWh - 0 means it hasn't been set yet int64_t mSessionEnergyDischargedAtStart = 0; // in mWh - 0 means it hasn't been set yet @@ -358,7 +370,7 @@ class EnergyEvseDelegate : public EnergyEvse::Delegate DataModel::Nullable mVehicleID; /* Session Object */ - EvseSession mSession = EvseSession(mEndpointId); + EvseSession mSession = EvseSession(); /* Helper variable to hold meter val since last EnergyTransferStarted event */ int64_t mMeterValueAtEnergyTransferStart; diff --git a/examples/energy-management-app/energy-management-common/energy-evse/src/EnergyEvseDelegateImpl.cpp b/examples/energy-management-app/energy-management-common/energy-evse/src/EnergyEvseDelegateImpl.cpp index 6266be245c2a4a..0294a0349e8f29 100644 --- a/examples/energy-management-app/energy-management-common/energy-evse/src/EnergyEvseDelegateImpl.cpp +++ b/examples/energy-management-app/energy-management-common/energy-evse/src/EnergyEvseDelegateImpl.cpp @@ -672,8 +672,8 @@ Status EnergyEvseDelegate::HandleEVPluggedInEvent() if (mState == StateEnum::kNotPluggedIn) { /* EV was not plugged in - start a new session */ - // TODO get energy meter readings - mSession.StartSession(0, 0); + // TODO get energy meter readings - #35370 + mSession.StartSession(mEndpointId, 0, 0); SendEVConnectedEvent(); /* Set the state to either PluggedInNoDemand or PluggedInDemand as indicated by mHwState */ @@ -694,6 +694,8 @@ Status EnergyEvseDelegate::HandleEVNotDetectedEvent() SendEnergyTransferStoppedEvent(EnergyTransferStoppedReasonEnum::kOther); } + // TODO get energy meter readings - #35370 + mSession.StopSession(mEndpointId, 0, 0); SendEVNotDetectedEvent(); SetState(StateEnum::kNotPluggedIn); return Status::Success; @@ -706,7 +708,7 @@ Status EnergyEvseDelegate::HandleEVNoDemandEvent() /* * EV was transferring current - EV decided to stop */ - mSession.RecalculateSessionDuration(); + mSession.RecalculateSessionDuration(mEndpointId); SendEnergyTransferStoppedEvent(EnergyTransferStoppedReasonEnum::kEVStopped); } /* We must still be plugged in to get here - so no need to check if we are plugged in! */ @@ -1601,7 +1603,6 @@ DataModel::Nullable EnergyEvseDelegate::GetSessionID() } DataModel::Nullable EnergyEvseDelegate::GetSessionDuration() { - mSession.RecalculateSessionDuration(); return mSession.mSessionDuration; } DataModel::Nullable EnergyEvseDelegate::GetSessionEnergyCharged() @@ -1626,10 +1627,11 @@ bool EnergyEvseDelegate::IsEvsePluggedIn() /** * @brief This function samples the start-time, and energy meter to hold the session info * + * @param endpointId - The endpoint to report the update on * @param chargingMeterValue - The current value of the energy meter (charging) in mWh * @param dischargingMeterValue - The current value of the energy meter (discharging) in mWh */ -void EvseSession::StartSession(int64_t chargingMeterValue, int64_t dischargingMeterValue) +void EvseSession::StartSession(EndpointId endpointId, int64_t chargingMeterValue, int64_t dischargingMeterValue) { /* Get Timestamp */ uint32_t chipEpoch = 0; @@ -1661,13 +1663,13 @@ void EvseSession::StartSession(int64_t chargingMeterValue, int64_t dischargingMe mSessionEnergyCharged = MakeNullable(static_cast(0)); mSessionEnergyDischarged = MakeNullable(static_cast(0)); - MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, SessionID::Id); - MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, SessionDuration::Id); - MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, SessionEnergyCharged::Id); - MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, SessionEnergyDischarged::Id); + MatterReportingAttributeChangeCallback(endpointId, EnergyEvse::Id, SessionID::Id); + MatterReportingAttributeChangeCallback(endpointId, EnergyEvse::Id, SessionDuration::Id); + MatterReportingAttributeChangeCallback(endpointId, EnergyEvse::Id, SessionEnergyCharged::Id); + MatterReportingAttributeChangeCallback(endpointId, EnergyEvse::Id, SessionEnergyDischarged::Id); // Write values to persistent storage. - ConcreteAttributePath path = ConcreteAttributePath(mEndpointId, EnergyEvse::Id, SessionID::Id); + ConcreteAttributePath path = ConcreteAttributePath(endpointId, EnergyEvse::Id, SessionID::Id); GetSafeAttributePersistenceProvider()->WriteScalarValue(path, mSessionID); // TODO persist mStartTime @@ -1675,12 +1677,28 @@ void EvseSession::StartSession(int64_t chargingMeterValue, int64_t dischargingMe // TODO persist mSessionEnergyDischargedAtStart } +/** + * @brief This function updates the session information at the unplugged event + * + * @param endpointId - The endpoint to report the update on + * @param chargingMeterValue - The current value of the energy meter (charging) in mWh + * @param dischargingMeterValue - The current value of the energy meter (discharging) in mWh + */ +void EvseSession::StopSession(EndpointId endpointId, int64_t chargingMeterValue, int64_t dischargingMeterValue) +{ + RecalculateSessionDuration(endpointId); + UpdateEnergyCharged(endpointId, chargingMeterValue); + UpdateEnergyDischarged(endpointId, dischargingMeterValue); +} + /*---------------------- EvseSession functions --------------------------*/ /** * @brief This function updates the session attrs to allow read attributes to return latest values + * + * @param endpointId - The endpoint to report the update on */ -void EvseSession::RecalculateSessionDuration() +void EvseSession::RecalculateSessionDuration(EndpointId endpointId) { /* Get Timestamp */ uint32_t chipEpoch = 0; @@ -1696,27 +1714,29 @@ void EvseSession::RecalculateSessionDuration() uint32_t duration = chipEpoch - mStartTime; mSessionDuration = MakeNullable(duration); - MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, SessionDuration::Id); + MatterReportingAttributeChangeCallback(endpointId, EnergyEvse::Id, SessionDuration::Id); } /** * @brief This function updates the EnergyCharged meter value * + * @param endpointId - The endpoint to report the update on * @param chargingMeterValue - The value of the energy meter (charging) in mWh */ -void EvseSession::UpdateEnergyCharged(int64_t chargingMeterValue) +void EvseSession::UpdateEnergyCharged(EndpointId endpointId, int64_t chargingMeterValue) { mSessionEnergyCharged = MakeNullable(chargingMeterValue - mSessionEnergyChargedAtStart); - MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, SessionEnergyCharged::Id); + MatterReportingAttributeChangeCallback(endpointId, EnergyEvse::Id, SessionEnergyCharged::Id); } /** * @brief This function updates the EnergyDischarged meter value * + * @param endpointId - The endpoint to report the update on * @param dischargingMeterValue - The value of the energy meter (discharging) in mWh */ -void EvseSession::UpdateEnergyDischarged(int64_t dischargingMeterValue) +void EvseSession::UpdateEnergyDischarged(EndpointId endpointId, int64_t dischargingMeterValue) { mSessionEnergyDischarged = MakeNullable(dischargingMeterValue - mSessionEnergyDischargedAtStart); - MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, SessionEnergyDischarged::Id); + MatterReportingAttributeChangeCallback(endpointId, EnergyEvse::Id, SessionEnergyDischarged::Id); } diff --git a/src/python_testing/TC_EEVSE_2_6.py b/src/python_testing/TC_EEVSE_2_6.py new file mode 100644 index 00000000000000..4567f96c3e466a --- /dev/null +++ b/src/python_testing/TC_EEVSE_2_6.py @@ -0,0 +1,293 @@ +# +# Copyright (c) 2024 Project CHIP Authors +# All rights reserved. +# +# 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. + +# See https://github.com/project-chip/connectedhomeip/blob/master/docs/testing/python.md#defining-the-ci-test-arguments +# for details about the block below. +# +# === BEGIN CI TEST ARGUMENTS === +# test-runner-runs: run1 +# test-runner-run/run1/app: ${ENERGY_MANAGEMENT_APP} +# test-runner-run/run1/factoryreset: True +# test-runner-run/run1/quiet: True +# test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json --enable-key 000102030405060708090a0b0c0d0e0f --application evse +# test-runner-run/run1/script-args: --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --hex-arg enableKey:000102030405060708090a0b0c0d0e0f --endpoint 1 --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto +# === END CI TEST ARGUMENTS === + +import logging +import time + +import chip.clusters as Clusters +from chip.clusters.Types import NullValue +from matter_testing_support import (ClusterAttributeChangeAccumulator, EventChangeCallback, MatterBaseTest, TestStep, + async_test_body, default_matter_test_main) +from mobly import asserts +from TC_EEVSE_Utils import EEVSEBaseTestHelper + +logger = logging.getLogger(__name__) + + +class TC_EEVSE_2_6(MatterBaseTest, EEVSEBaseTestHelper): + + def desc_TC_EEVSE_2_6(self) -> str: + """Returns a description of this test""" + return "5.1.6. [TC-EEVSE-2.6] Test Q quality functionality with DUT as Server" + + def pics_TC_EEVSE_2_6(self): + """ This function returns a list of PICS for this test case that must be True for the test to be run""" + + return ["EEVSE.S"] + + def steps_TC_EEVSE_2_6(self) -> list[TestStep]: + steps = [ + TestStep("1", "Commission DUT to TH (can be skipped if done in a preceding test)"), + TestStep("2", "TH reads from the DUT the FeatureMap", + "Verify that the DUT response contains the FeatureMap attribute. Store the value as FeatureMap."), + TestStep("3", "Set up a subscription to all EnergyEVSE cluster events"), + TestStep("4", "TH reads TestEventTriggersEnabled attribute from General Diagnostics Cluster", + "Value has to be 1 (True)"), + TestStep("5", "Set up a subscription to the EnergyEVSE cluster, with MinIntervalFloor set to 0, MaxIntervalCeiling set to 10 and KeepSubscriptions set to True", + "Subscription successfully established"), + TestStep("6", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TESTEVENT_TRIGGERKEY and EventTrigger field set to PIXIT.EEVSE.TESTEVENTTRIGGER for Basic Functionality Test Event", + "Verify DUT responds w/ status SUCCESS(0x00)"), + TestStep("6a", "TH reads from the DUT the State", + "Value has to be 0x00 (NotPluggedIn)"), + TestStep("7", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TESTEVENT_TRIGGERKEY and EventTrigger field set to PIXIT.EEVSE.TESTEVENTTRIGGER for EV Plugged-in Test Event", + "Verify DUT responds w/ status SUCCESS(0x00) and event EEVSE.S.E00(EVConnected) sent"), + TestStep("8", "TH sends command EnableCharging with ChargingEnabledUntil=null, minimumChargeCurrent=6000, maximumChargeCurrent=12000", + "Verify DUT responds w/ status SUCCESS(0x00)"), + TestStep("9", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TESTEVENT_TRIGGERKEY and EventTrigger field set to PIXIT.EEVSE.TESTEVENTTRIGGER for EV Charge Demand Test Event", + "Verify DUT responds w/ status SUCCESS(0x00) and event EEVSE.S.E02(EnergyTransferStarted) sent"), + TestStep("9a", "TH reads from the DUT the State", + "Value has to be 0x03 (PluggedInCharging)"), + TestStep("10", "Reset all accumulated report counts, then wait 12 seconds"), + TestStep("10a", "TH counts all report transactions with an attribute report for the SessionID attribute", + "TH verifies that numberOfReportsReceived = 0"), + TestStep("10b", "TH counts all report transactions with an attribute report for the SessionDuration attribute", + "TH verifies that numberOfReportsReceived <= 2"), + TestStep("10c", "TH counts all report transactions with an attribute report for the SessionEnergyCharged attribute", + "TH verifies that numberOfReportsReceived <= 2"), + TestStep("10d", "TH counts all report transactions with an attribute report for the SessionEnergyDischarged attribute", + "TH verifies that numberOfReportsReceived <= 2"), + TestStep("11", "Reset all accumulated report counts"), + TestStep("12", "TH sends command Disable", + "Verify DUT responds w/ status SUCCESS(0x00) and Event EEVSE.S.E03(EnergyTransferStopped) sent with reason EvseStopped"), + TestStep("13", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TESTEVENT_TRIGGERKEY and EventTrigger field set to PIXIT.EEVSE.TESTEVENTTRIGGER for EV Charge Demand Test Event Clear", + "Verify DUT responds w/ status SUCCESS(0x00)"), + TestStep("14", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TESTEVENT_TRIGGERKEY and EventTrigger field set to PIXIT.EEVSE.TESTEVENTTRIGGER for EV Plugged-in Test Event Clear", + "Verify DUT responds w/ status SUCCESS(0x00) and event EEVSE.S.E01(EVNotDetected) sent"), + TestStep("15", "Wait 5 seconds"), + TestStep("15a", "TH counts all report transactions with an attribute report for the SessionID attribute", + "TH verifies that numberOfReportsReceived = 0"), + TestStep("15b", "TH counts all report transactions with an attribute report for the SessionDuration attribute", + "TH verifies that numberOfReportsReceived >= 1"), + TestStep("15c", "TH counts all report transactions with an attribute report for the SessionEnergyCharged attribute", + "TH verifies that numberOfReportsReceived >= 1"), + TestStep("15d", "If V2X feature is supported on the cluster, TH counts all report transactions with an attribute report for the SessionEnergyDischarged attribute", + "TH verifies that numberOfReportsReceived >= 1"), + TestStep("16", "Reset all accumulated report counts"), + TestStep("17", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TESTEVENT_TRIGGERKEY and EventTrigger field set to PIXIT.EEVSE.TESTEVENTTRIGGER for EV Plugged-in Test Event", + "Verify DUT responds w/ status SUCCESS(0x00) and event EEVSE.S.E00(EVConnected) sent"), + TestStep("18", "Wait 5 seconds"), + TestStep("18a", "TH counts all report transactions with an attribute report for the SessionID attribute", + "TH verifies that numberOfReportsReceived = 1"), + TestStep("18b", "TH counts all report transactions with an attribute report for the SessionDuration attribute", + "TH verifies that numberOfReportsReceived >= 1"), + TestStep("18c", "TH counts all report transactions with an attribute report for the SessionEnergyCharged attribute", + "TH verifies that numberOfReportsReceived >= 1"), + TestStep("18d", "If V2X feature is supported on the cluster, TH counts all report transactions with an attribute report for the SessionEnergyDischarged attribute", + "TH verifies that numberOfReportsReceived >= 1"), + TestStep("19", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TESTEVENT_TRIGGERKEY and EventTrigger field set to PIXIT.EEVSE.TESTEVENTTRIGGER for EV Plugged-in Test Event Clear", + "Verify DUT responds w/ status SUCCESS(0x00) and event EEVSE.S.E01(EVNotDetected) sent"), + TestStep("20", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TESTEVENT_TRIGGERKEY and EventTrigger field set to PIXIT.EEVSE.TESTEVENTTRIGGER for Basic Functionality Test Event Clear", + "Verify DUT responds w/ status SUCCESS(0x00)"), + TestStep("21", "Cancel the subscription to the Device Energy Management cluster", + "The subscription is cancelled successfully"), + ] + + return steps + + @async_test_body + async def test_TC_EEVSE_2_6(self): + self.step("1") + # Commission DUT - already done + + self.step("2") + feature_map = await self.read_evse_attribute_expect_success(attribute="FeatureMap") + logger.info(f"FeatureMap: {feature_map}") + has_v2x = feature_map & Clusters.EnergyEvse.Bitmaps.Feature.kV2x + + # Subscribe to Events and when they are sent push them to a queue for checking later + self.step("3") + events_callback = EventChangeCallback(Clusters.EnergyEvse) + await events_callback.start(self.default_controller, + self.dut_node_id, + self.matter_test_config.endpoint) + + self.step("4") + await self.check_test_event_triggers_enabled() + + self.step("5") + sub_handler = ClusterAttributeChangeAccumulator(Clusters.EnergyEvse) + await sub_handler.start(self.default_controller, self.dut_node_id, + self.matter_test_config.endpoint, + min_interval_sec=0, + max_interval_sec=10, keepSubscriptions=True) + + def accumulate_reports(wait_time): + logging.info(f"Test will now wait {wait_time} seconds to accumulate reports") + time.sleep(wait_time) + + self.step("6") + await self.send_test_event_trigger_basic() + + self.step("6a") + await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kNotPluggedIn) + + self.step("7") + await self.send_test_event_trigger_pluggedin() + event_data = events_callback.wait_for_event_report( + Clusters.EnergyEvse.Events.EVConnected) + + self.step("8") + charge_until = NullValue + min_charge_current = 6000 + max_charge_current = 12000 + await self.send_enable_charge_command(charge_until=charge_until, min_charge=min_charge_current, max_charge=max_charge_current) + + self.step("9") + await self.send_test_event_trigger_charge_demand() + event_data = events_callback.wait_for_event_report(Clusters.EnergyEvse.Events.EnergyTransferStarted) + + self.step("9a") + await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kPluggedInCharging) + + self.step("10") + wait = 12 # Wait 12 seconds - the spec says we should only get reports every 10s at most, unless a command changes it + sub_handler.reset() + accumulate_reports(wait) + + self.step("10a") + count = sub_handler.attribute_report_counts[Clusters.EnergyEvse.Attributes.SessionID] + logging.info(f"Received {count} SessionID updates in {wait} seconds") + asserts.assert_equal(count, 0, f"Expected NO SessionID updates in {wait} seconds") + + self.step("10b") + count = sub_handler.attribute_report_counts[Clusters.EnergyEvse.Attributes.SessionDuration] + logging.info(f"Received {count} SessionDuration updates in {wait} seconds") + asserts.assert_less_equal(count, 2, f"Expected <= 2 SessionDuration updates in {wait} seconds") + + self.step("10c") + count = sub_handler.attribute_report_counts[Clusters.EnergyEvse.Attributes.SessionEnergyCharged] + logging.info(f"Received {count} SessionEnergyCharged updates in {wait} seconds") + asserts.assert_less_equal(count, 2, f"Expected <= 2 SessionEnergyCharged updates in {wait} seconds") + + self.step("10d") + if has_v2x: + count = sub_handler.attribute_report_counts[Clusters.EnergyEvse.Attributes.SessionEnergyDischarged] + logging.info(f"Received {count} SessionEnergyDischarged updates in {wait} seconds") + asserts.assert_less_equal(count, 2, f"Expected <= 2 SessionEnergyDischarged updates in {wait} seconds") + + self.step("11") + sub_handler.reset() + + self.step("12") + await self.send_disable_command() + event_data = events_callback.wait_for_event_report( + Clusters.EnergyEvse.Events.EnergyTransferStopped) + expected_reason = Clusters.EnergyEvse.Enums.EnergyTransferStoppedReasonEnum.kEVSEStopped + asserts.assert_equal(expected_reason, event_data.reason, + f"EnergyTransferStopped event reason was {event_data.reason}, expected {expected_reason}") + + self.step("13") + await self.send_test_event_trigger_charge_demand_clear() + + self.step("14") + await self.send_test_event_trigger_pluggedin_clear() + event_data = events_callback.wait_for_event_report( + Clusters.EnergyEvse.Events.EVNotDetected) + + self.step("15") + wait = 5 # We expect a change to the Session attributes after the EV is unplugged, Wait 5 seconds - allow time for the report to come in + accumulate_reports(wait) + + self.step("15a") + count = sub_handler.attribute_report_counts[Clusters.EnergyEvse.Attributes.SessionID] + logging.info(f"Received {count} SessionID updates in {wait} seconds") + asserts.assert_equal(count, 0, "Expected = 0 SessionID updates after a Unplugged operation - it changes on next plug-in") + + self.step("15b") + count = sub_handler.attribute_report_counts[Clusters.EnergyEvse.Attributes.SessionDuration] + logging.info(f"Received {count} SessionDuration updates in {wait} seconds") + asserts.assert_greater_equal(count, 1, "Expected >= 1 SessionDuration updates after a Unplugged operation") + + self.step("15c") + count = sub_handler.attribute_report_counts[Clusters.EnergyEvse.Attributes.SessionEnergyCharged] + logging.info(f"Received {count} SessionEnergyCharged updates in {wait} seconds") + asserts.assert_greater_equal(count, 1, "Expected >= 1 SessionEnergyCharged updates after a Unplugged operation") + + self.step("15d") + if has_v2x: + count = sub_handler.attribute_report_counts[Clusters.EnergyEvse.Attributes.SessionEnergyDischarged] + logging.info(f"Received {count} SessionEnergyDischarged updates in {wait} seconds") + asserts.assert_greater_equal(count, 1, "Expected >= 1 SessionEnergyDischarged updates after a Unplugged operation") + + self.step("16") + sub_handler.reset() + + self.step("17") + await self.send_test_event_trigger_pluggedin() + event_data = events_callback.wait_for_event_report( + Clusters.EnergyEvse.Events.EVConnected) + + self.step("18") + wait = 5 # We expect a change to the Session attributes after the EV is plugged in again, Wait 5 seconds - allow time for the report to come in + accumulate_reports(wait) + + self.step("18a") + count = sub_handler.attribute_report_counts[Clusters.EnergyEvse.Attributes.SessionID] + logging.info(f"Received {count} SessionID updates in {wait} seconds") + asserts.assert_equal(count, 1, "Expected = 1 SessionID updates after a plug-in") + + self.step("18b") + count = sub_handler.attribute_report_counts[Clusters.EnergyEvse.Attributes.SessionDuration] + logging.info(f"Received {count} SessionDuration updates in {wait} seconds") + asserts.assert_greater_equal(count, 1, "Expected >= 1 SessionDuration updates after a Unplugged operation") + + self.step("18c") + count = sub_handler.attribute_report_counts[Clusters.EnergyEvse.Attributes.SessionEnergyCharged] + logging.info(f"Received {count} SessionEnergyCharged updates in {wait} seconds") + asserts.assert_greater_equal(count, 1, "Expected >= 1 SessionEnergyCharged updates after a Unplugged operation") + + self.step("18d") + if has_v2x: + count = sub_handler.attribute_report_counts[Clusters.EnergyEvse.Attributes.SessionEnergyDischarged] + logging.info(f"Received {count} SessionEnergyDischarged updates in {wait} seconds") + asserts.assert_greater_equal(count, 1, "Expected >= 1 SessionEnergyDischarged updates after a Unplugged operation") + + self.step("19") + await self.send_test_event_trigger_pluggedin_clear() + event_data = events_callback.wait_for_event_report( + Clusters.EnergyEvse.Events.EVNotDetected) + + self.step("20") + await self.send_test_event_trigger_basic_clear() + + self.step("21") + await sub_handler.cancel() + + +if __name__ == "__main__": + default_matter_test_main() From d9cfa6a6a8b45b2f6c02489e566f6867f8523d1b Mon Sep 17 00:00:00 2001 From: Tennessee Carmel-Veilleux Date: Tue, 3 Sep 2024 16:36:37 -0400 Subject: [PATCH 02/15] Make at least on/off light chef have endpoint ID > 10 (#35371) - To test corner cases where tests assume the single application endpoint is endpoint 1, this makes the only application endpoint be 13 in at least one sample. --- examples/chef/devices/rootnode_onofflight_bbs1b7IaOV.matter | 2 +- examples/chef/devices/rootnode_onofflight_bbs1b7IaOV.zap | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/chef/devices/rootnode_onofflight_bbs1b7IaOV.matter b/examples/chef/devices/rootnode_onofflight_bbs1b7IaOV.matter index 4627385f5a4c44..9d4aa0c7362d22 100644 --- a/examples/chef/devices/rootnode_onofflight_bbs1b7IaOV.matter +++ b/examples/chef/devices/rootnode_onofflight_bbs1b7IaOV.matter @@ -2032,7 +2032,7 @@ endpoint 0 { ram attribute clusterRevision default = 1; } } -endpoint 1 { +endpoint 13 { device type ma_onofflight = 256, version 1; binding cluster Binding; diff --git a/examples/chef/devices/rootnode_onofflight_bbs1b7IaOV.zap b/examples/chef/devices/rootnode_onofflight_bbs1b7IaOV.zap index e8625f2f612caf..aa402e3f0b0df2 100644 --- a/examples/chef/devices/rootnode_onofflight_bbs1b7IaOV.zap +++ b/examples/chef/devices/rootnode_onofflight_bbs1b7IaOV.zap @@ -3473,9 +3473,9 @@ "endpointTypeName": "Anonymous Endpoint Type", "endpointTypeIndex": 1, "profileId": 259, - "endpointId": 1, + "endpointId": 13, "networkId": 0, "parentEndpointIdentifier": null } ] -} \ No newline at end of file +} From a704a384a5343b47c8d61ba8d5f3b0e3aba6cd0c Mon Sep 17 00:00:00 2001 From: C Freeman Date: Tue, 3 Sep 2024 16:53:30 -0400 Subject: [PATCH 03/15] TC-IDM-XX: try both PASE and CASE for basic comp tests. (#35109) * TC-IDM-XX: try both PASE and CASE for basic comp tests. These tests are disruptive at cert because they could run over PASE or CASE and defaulted to PASE. This meant that they couldn't be run in sequence with other tests that require commissioning. Because the PASE setup required a qr or manual code, it also meant that these needed special parameters to run. Now: - can use discriminator and passcode for PASE connection - device tries both PASE and CASE and runs over whichever works first. * fix CI * Restyled by whitespace * Restyled by clang-format * Fix tests for NO DUT (unit and mock) * Update src/python_testing/basic_composition_support.py Co-authored-by: Tennessee Carmel-Veilleux * Null terminate and add outbuf size * Restyled by clang-format * typo --------- Co-authored-by: Restyled.io Co-authored-by: Tennessee Carmel-Veilleux --- .github/workflows/tests.yaml | 4 +- .../python/ChipDeviceController-Discovery.cpp | 31 ++++++++++++ src/controller/python/chip/ChipDeviceCtrl.py | 16 +++++++ .../TC_DeviceBasicComposition.py | 46 +++++++++++++++++- .../basic_composition_support.py | 48 +++++++++++++------ src/python_testing/matter_testing_support.py | 38 ++++++++------- 6 files changed, 147 insertions(+), 36 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 9fcfc5cc5c033c..a99cc0a24d1f98 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -515,8 +515,6 @@ jobs: mkdir -p out/trace_data scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/controller/python/test/test_scripts/mobile-device-test.py' scripts/run_in_python_env.sh out/venv 'python3 ./src/python_testing/execute_python_tests.py --env-file /tmp/test_env.yaml --search-directory src/python_testing' - 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"' - 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"' scripts/run_in_python_env.sh out/venv './scripts/tests/TestTimeSyncTrustedTimeSourceRunner.py' scripts/run_in_python_env.sh out/venv './src/python_testing/test_testing/test_TC_ICDM_2_1.py' scripts/run_in_python_env.sh out/venv 'python3 ./src/python_testing/TestIdChecks.py' @@ -528,6 +526,8 @@ jobs: scripts/run_in_python_env.sh out/venv 'python3 ./src/python_testing/test_testing/test_IDM_10_4.py' scripts/run_in_python_env.sh out/venv 'python3 ./src/python_testing/test_testing/test_TC_SC_7_1.py' scripts/run_in_python_env.sh out/venv 'python3 ./src/python_testing/test_testing/TestDecorators.py' + scripts/run_in_python_env.sh out/venv 'python3 ./src/python_testing/TestMatterTestingSupport.py' + scripts/run_in_python_env.sh out/venv 'python3 ./src/python_testing/TestSpecParsingSupport.py' - name: Uploading core files diff --git a/src/controller/python/ChipDeviceController-Discovery.cpp b/src/controller/python/ChipDeviceController-Discovery.cpp index 7c669603f4ff1d..e4349b31bd1f09 100644 --- a/src/controller/python/ChipDeviceController-Discovery.cpp +++ b/src/controller/python/ChipDeviceController-Discovery.cpp @@ -23,12 +23,16 @@ * */ +#include + #include #include #include #include #include #include +#include +#include using namespace chip; @@ -186,4 +190,31 @@ bool pychip_DeviceController_GetIPForDiscoveredDevice(Controller::DeviceCommissi } return false; } + +PyChipError pychip_CreateManualCode(uint16_t longDiscriminator, uint32_t passcode, char * manualCodeBuffer, size_t inBufSize, + size_t * outBufSize) +{ + SetupPayload payload; + SetupDiscriminator discriminator; + discriminator.SetLongValue(longDiscriminator); + payload.discriminator = discriminator; + payload.setUpPINCode = passcode; + std::string setupManualCode; + + *outBufSize = 0; + CHIP_ERROR err = ManualSetupPayloadGenerator(payload).payloadDecimalStringRepresentation(setupManualCode); + if (err == CHIP_NO_ERROR) + { + MutableCharSpan span(manualCodeBuffer, inBufSize); + // Plus 1 so we copy the null terminator + CopyCharSpanToMutableCharSpan(CharSpan(setupManualCode.c_str(), setupManualCode.length() + 1), span); + *outBufSize = span.size(); + if (*outBufSize == 0) + { + err = CHIP_ERROR_NO_MEMORY; + } + } + + return ToPyChipError(err); +} } diff --git a/src/controller/python/chip/ChipDeviceCtrl.py b/src/controller/python/chip/ChipDeviceCtrl.py index 24fe59486f78d5..011174185fd440 100644 --- a/src/controller/python/chip/ChipDeviceCtrl.py +++ b/src/controller/python/chip/ChipDeviceCtrl.py @@ -1711,6 +1711,19 @@ def InitGroupTestingData(self): self.devCtrl) ).raise_on_error() + def CreateManualCode(self, discriminator: int, passcode: int) -> str: + """ Creates a standard flow manual code from the given discriminator and passcode.""" + # 64 bytes is WAY more than required, but let's be safe + in_size = 64 + out_size = c_size_t(0) + buf = create_string_buffer(in_size) + self._ChipStack.Call( + lambda: self._dmLib.pychip_CreateManualCode(discriminator, passcode, buf, in_size, pointer(out_size)) + ).raise_on_error() + if out_size.value == 0 or out_size.value > in_size: + raise MemoryError("Invalid output size for manual code") + return buf.value.decode() + # ----- Private Members ----- def _InitLib(self): if self._dmLib is None: @@ -1938,6 +1951,9 @@ def _InitLib(self): self._dmLib.pychip_DeviceProxy_GetRemoteSessionParameters.restype = PyChipError self._dmLib.pychip_DeviceProxy_GetRemoteSessionParameters.argtypes = [c_void_p, c_char_p] + self._dmLib.pychip_CreateManualCode.restype = PyChipError + self._dmLib.pychip_CreateManualCode.argtypes = [c_uint16, c_uint32, c_char_p, c_size_t, POINTER(c_size_t)] + class ChipDeviceController(ChipDeviceControllerBase): ''' The ChipDeviceCommissioner binding, named as ChipDeviceController diff --git a/src/python_testing/TC_DeviceBasicComposition.py b/src/python_testing/TC_DeviceBasicComposition.py index 72e6e3e2418c8c..24a336e0b001e9 100644 --- a/src/python_testing/TC_DeviceBasicComposition.py +++ b/src/python_testing/TC_DeviceBasicComposition.py @@ -19,14 +19,58 @@ # for details about the block below. # # === BEGIN CI TEST ARGUMENTS === -# test-runner-runs: run1 +# test-runner-runs: run1 run2 run3 run4 run5 run6 run7 # test-runner-run/run1/app: ${ALL_CLUSTERS_APP} # test-runner-run/run1/factoryreset: True # test-runner-run/run1/quiet: True # test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json # test-runner-run/run1/script-args: --storage-path admin_storage.json --manual-code 10054912339 --PICS src/app/tests/suites/certification/ci-pics-values --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto +# +# test-runner-run/run2/app: ${CHIP_LOCK_APP} +# test-runner-run/run2/factoryreset: True +# test-runner-run/run2/quiet: True +# test-runner-run/run2/app-args: --discriminator 1234 --KVS kvs1 +# test-runner-run/run2/script-args: --storage-path admin_storage.json --manual-code 10054912339 +# +# test-runner-run/run3/app: ${CHIP_LOCK_APP} +# test-runner-run/run3/factoryreset: True +# test-runner-run/run3/quiet: True +# test-runner-run/run3/app-args: --discriminator 1234 --KVS kvs1 +# test-runner-run/run3/script-args: --storage-path admin_storage.json --qr-code MT:-24J0Q1212-10648G00 +# +# test-runner-run/run4/app: ${CHIP_LOCK_APP} +# test-runner-run/run4/factoryreset: True +# test-runner-run/run4/quiet: True +# test-runner-run/run4/app-args: --discriminator 1234 --KVS kvs1 +# test-runner-run/run4/script-args: --storage-path admin_storage.json --discriminator 1234 --passcode 20202021 +# +# test-runner-run/run5/app: ${CHIP_LOCK_APP} +# test-runner-run/run5/factoryreset: True +# test-runner-run/run5/quiet: True +# test-runner-run/run5/app-args: --discriminator 1234 --KVS kvs1 +# test-runner-run/run5/script-args: --storage-path admin_storage.json --manual-code 10054912339 --commissioning-method on-network +# +# test-runner-run/run6/app: ${CHIP_LOCK_APP} +# test-runner-run/run6/factoryreset: True +# test-runner-run/run6/quiet: True +# test-runner-run/run6/app-args: --discriminator 1234 --KVS kvs1 +# test-runner-run/run6/script-args: --storage-path admin_storage.json --qr-code MT:-24J0Q1212-10648G00 --commissioning-method on-network +# +# test-runner-run/run7/app: ${CHIP_LOCK_APP} +# test-runner-run/run7/factoryreset: True +# test-runner-run/run7/quiet: True +# test-runner-run/run7/app-args: --discriminator 1234 --KVS kvs1 +# test-runner-run/run7/script-args: --storage-path admin_storage.json --discriminator 1234 --passcode 20202021 --commissioning-method on-network # === END CI TEST ARGUMENTS === +# Run 1: runs through all tests +# Run 2: tests PASE connection using manual code (12.1 only) +# Run 3: tests PASE connection using QR code (12.1 only) +# Run 4: tests PASE connection using discriminator and passcode (12.1 only) +# Run 5: Tests CASE connection using manual code (12.1 only) +# Run 6: Tests CASE connection using QR code (12.1 only) +# Run 7: Tests CASE connection using manual discriminator and passcode (12.1 only) + import logging from dataclasses import dataclass from typing import Any, Callable diff --git a/src/python_testing/basic_composition_support.py b/src/python_testing/basic_composition_support.py index e25de55c0441a9..c09d78ef3027d8 100644 --- a/src/python_testing/basic_composition_support.py +++ b/src/python_testing/basic_composition_support.py @@ -15,7 +15,7 @@ # limitations under the License. # - +import asyncio import base64 import copy import json @@ -98,12 +98,15 @@ def ConvertValue(value) -> Any: class BasicCompositionTests: - async def connect_over_pase(self, dev_ctrl): - asserts.assert_true(self.matter_test_config.qr_code_content == [] or self.matter_test_config.manual_code == [], - "Cannot have both QR and manual code specified") - setupCode = self.matter_test_config.qr_code_content + self.matter_test_config.manual_code - asserts.assert_equal(len(setupCode), 1, "Require one of either --qr-code or --manual-code.") - await dev_ctrl.FindOrEstablishPASESession(setupCode[0], self.dut_node_id) + def get_code(self, dev_ctrl): + created_codes = [] + for idx, discriminator in enumerate(self.matter_test_config.discriminators): + created_codes.append(dev_ctrl.CreateManualCode(discriminator, self.matter_test_config.setup_passcodes[idx])) + + setup_codes = self.matter_test_config.qr_code_content + self.matter_test_config.manual_code + created_codes + asserts.assert_equal(len(setup_codes), 1, + "Require exactly one of either --qr-code, --manual-code or (--discriminator and --passcode).") + return setup_codes[0] def dump_wildcard(self, dump_device_composition_path: typing.Optional[str]) -> tuple[str, str]: """ Dumps a json and a txt file of the attribute wildcard for this device if the dump_device_composition_path is supplied. @@ -120,19 +123,34 @@ def dump_wildcard(self, dump_device_composition_path: typing.Optional[str]) -> t pprint(self.endpoints, outfile, indent=1, width=200, compact=True) return (json_dump_string, pformat(self.endpoints, indent=1, width=200, compact=True)) - async def setup_class_helper(self, default_to_pase: bool = True): + async def setup_class_helper(self, allow_pase: bool = True): dev_ctrl = self.default_controller self.problems = [] - do_test_over_pase = self.user_params.get("use_pase_only", default_to_pase) dump_device_composition_path: Optional[str] = self.user_params.get("dump_device_composition_path", None) - if do_test_over_pase: - await self.connect_over_pase(dev_ctrl) - node_id = self.dut_node_id - else: - # Using the already commissioned node - node_id = self.dut_node_id + node_id = self.dut_node_id + + task_list = [] + if allow_pase: + setup_code = self.get_code(dev_ctrl) + pase_future = dev_ctrl.EstablishPASESession(setup_code, self.dut_node_id) + task_list.append(asyncio.create_task(pase_future)) + + case_future = dev_ctrl.GetConnectedDevice(nodeid=node_id, allowPASE=False) + task_list.append(asyncio.create_task(case_future)) + + for task in task_list: + asyncio.ensure_future(task) + + done, pending = await asyncio.wait(task_list, return_when=asyncio.FIRST_COMPLETED) + + for task in pending: + try: + task.cancel() + await task + except asyncio.CancelledError: + pass wildcard_read = (await dev_ctrl.Read(node_id, [()])) diff --git a/src/python_testing/matter_testing_support.py b/src/python_testing/matter_testing_support.py index 1e0fa26ae8d722..65baf60e51752d 100644 --- a/src/python_testing/matter_testing_support.py +++ b/src/python_testing/matter_testing_support.py @@ -629,8 +629,8 @@ class MatterTestConfig: app_pid: int = 0 commissioning_method: Optional[str] = None - discriminators: Optional[List[int]] = None - setup_passcodes: Optional[List[int]] = None + discriminators: List[int] = field(default_factory=list) + setup_passcodes: List[int] = field(default_factory=list) commissionee_ip_address_just_for_testing: Optional[str] = None # By default, we start with maximized cert chains, as required for RR-1.1. # This allows cert tests to be run without re-commissioning for RR-1.1. @@ -646,7 +646,7 @@ class MatterTestConfig: pics: dict[bool, str] = field(default_factory=dict) # Node ID for basic DUT - dut_node_ids: Optional[List[int]] = None + dut_node_ids: List[int] = field(default_factory=list) # Node ID to use for controller/commissioner controller_node_id: int = _DEFAULT_CONTROLLER_NODE_ID # CAT Tags for default controller/commissioner @@ -1730,19 +1730,8 @@ def populate_commissioning_args(args: argparse.Namespace, config: MatterTestConf config.qr_code_content.extend(args.qr_code) config.manual_code.extend(args.manual_code) - - if args.commissioning_method is None: - return True - - if args.discriminators == [] and (args.qr_code == [] and args.manual_code == []): - print("error: Missing --discriminator when no --qr-code/--manual-code present!") - return False - config.discriminators = args.discriminators - - if args.passcodes == [] and (args.qr_code == [] and args.manual_code == []): - print("error: Missing --passcode when no --qr-code/--manual-code present!") - return False - config.setup_passcodes = args.passcodes + config.discriminators.extend(args.discriminators) + config.setup_passcodes.extend(args.passcodes) if args.qr_code != [] and args.manual_code != []: print("error: Cannot have both --qr-code and --manual-code present!") @@ -1759,9 +1748,11 @@ def populate_commissioning_args(args: argparse.Namespace, config: MatterTestConf return False if len(config.dut_node_ids) < len(device_descriptors): - missing = len(device_descriptors) - len(config.dut_node_ids) # We generate new node IDs sequentially from the last one seen for all # missing NodeIDs when commissioning many nodes at once. + if not config.dut_node_ids: + config.dut_node_ids = [_DEFAULT_DUT_NODE_ID] + missing = len(device_descriptors) - len(config.dut_node_ids) for i in range(missing): config.dut_node_ids.append(config.dut_node_ids[-1] + 1) @@ -1773,6 +1764,17 @@ def populate_commissioning_args(args: argparse.Namespace, config: MatterTestConf print("error: Duplicate value in discriminator list") return False + if args.commissioning_method is None: + return True + + if args.discriminators == [] and (args.qr_code == [] and args.manual_code == []): + print("error: Missing --discriminator when no --qr-code/--manual-code present!") + return False + + if args.passcodes == [] and (args.qr_code == [] and args.manual_code == []): + print("error: Missing --passcode when no --qr-code/--manual-code present!") + return False + if config.commissioning_method == "ble-wifi": if args.wifi_ssid is None: print("error: missing --wifi-ssid for --commissioning-method ble-wifi!") @@ -1869,7 +1871,7 @@ def parse_matter_test_args(argv: Optional[List[str]] = None) -> MatterTestConfig default=_DEFAULT_CONTROLLER_NODE_ID, help='NodeID to use for initial/default controller (default: %d)' % _DEFAULT_CONTROLLER_NODE_ID) basic_group.add_argument('-n', '--dut-node-id', '--nodeId', type=int_decimal_or_hex, - metavar='NODE_ID', dest='dut_node_ids', default=[_DEFAULT_DUT_NODE_ID], + metavar='NODE_ID', dest='dut_node_ids', default=[], help='Node ID for primary DUT communication, ' 'and NodeID to assign if commissioning (default: %d)' % _DEFAULT_DUT_NODE_ID, nargs="+") basic_group.add_argument('--endpoint', type=int, default=0, help="Endpoint under test") From 91ac7b3ed9d845d00d9e2c8ce7ff9541c2c7fee9 Mon Sep 17 00:00:00 2001 From: C Freeman Date: Tue, 3 Sep 2024 17:07:20 -0400 Subject: [PATCH 04/15] python testing: Per node endpoint fixes (#34819) * Change name of decorator * Add a force option for per endpoint tests * fix test * Add warning on force endpoint * Disallow --endpoint with automatic endpoint selection * Restyled by isort * merge conflict fix * Don't automatically override endpoint in MockRunner * Restyled by isort * Fix endpoint default on command line * Going the other way on this With the way the ATLs expect the test files currently, this is causing issues with submission. The ATLs aren't familiar with how to read these files and it's making cert challenging. Instead, we're going to gate or skip tests PER ENDPOINT such that the --endpoint flag can be set. This also (in theory) lets the tests run alongside other tests, where the --endpoint is specified. The auto-runner was too much, too soon. It's great for CI, but we don't have TH features to select these tests, nor ways to allow the file submission. Let's fall back to once per endpoint. It's still better, we're not relying on PICS. * Update comments. * Update all tests * more things * fix singleton test * Restyled by isort * fix var * Update src/python_testing/matter_testing_support.py Co-authored-by: Boris Zbarsky * Restyled by isort * add endpoint flags --------- Co-authored-by: Restyled.io Co-authored-by: Boris Zbarsky --- src/python_testing/TC_CCTRL_2_1.py | 4 +- src/python_testing/TC_CCTRL_2_2.py | 4 +- src/python_testing/TC_CC_2_2.py | 6 +- src/python_testing/TC_LVL_2_3.py | 6 +- src/python_testing/TC_SWTCH.py | 39 +++- src/python_testing/TC_TIMESYNC_2_1.py | 6 +- src/python_testing/matter_testing_support.py | 170 +++++++------- .../test_testing/MockTestRunner.py | 17 +- .../test_testing/TestDecorators.py | 218 +++++++++++------- .../test_testing/test_TC_CCNTL_2_2.py | 2 +- .../test_testing/test_TC_MCORE_FS_1_1.py | 2 +- 11 files changed, 269 insertions(+), 205 deletions(-) diff --git a/src/python_testing/TC_CCTRL_2_1.py b/src/python_testing/TC_CCTRL_2_1.py index 7822d101b16e02..a8aedb49ab3f2c 100644 --- a/src/python_testing/TC_CCTRL_2_1.py +++ b/src/python_testing/TC_CCTRL_2_1.py @@ -16,7 +16,7 @@ # import chip.clusters as Clusters -from matter_testing_support import MatterBaseTest, TestStep, default_matter_test_main, has_cluster, per_endpoint_test +from matter_testing_support import MatterBaseTest, TestStep, default_matter_test_main, has_cluster, run_if_endpoint_matches from mobly import asserts @@ -27,7 +27,7 @@ def steps_TC_CCTRL_2_1(self) -> list[TestStep]: TestStep(2, "Validate SupportedDeviceCategories is set accordingly based on MCORE.FS")] return steps - @per_endpoint_test(has_cluster(Clusters.CommissionerControl)) + @run_if_endpoint_matches(has_cluster(Clusters.CommissionerControl)) async def test_TC_CCTRL_2_1(self): self.step(1) is_fabric_sync_pics_enabled = self.check_pics("MCORE.FS") diff --git a/src/python_testing/TC_CCTRL_2_2.py b/src/python_testing/TC_CCTRL_2_2.py index ed4051feda3ebb..aa52703313ceaa 100644 --- a/src/python_testing/TC_CCTRL_2_2.py +++ b/src/python_testing/TC_CCTRL_2_2.py @@ -34,7 +34,7 @@ from chip import ChipDeviceCtrl from chip.interaction_model import InteractionModelError, Status from matter_testing_support import (MatterBaseTest, TestStep, async_test_body, default_matter_test_main, has_cluster, - per_endpoint_test) + run_if_endpoint_matches) from mobly import asserts @@ -119,7 +119,7 @@ def steps_TC_CCTRL_2_2(self) -> list[TestStep]: return steps - @per_endpoint_test(has_cluster(Clusters.CommissionerControl)) + @run_if_endpoint_matches(has_cluster(Clusters.CommissionerControl)) async def test_TC_CCTRL_2_2(self): self.is_ci = self.check_pics('PICS_SDK_CI_ONLY') diff --git a/src/python_testing/TC_CC_2_2.py b/src/python_testing/TC_CC_2_2.py index 86080efd358456..2cd9aef8a09f35 100644 --- a/src/python_testing/TC_CC_2_2.py +++ b/src/python_testing/TC_CC_2_2.py @@ -24,7 +24,7 @@ # test-runner-run/run1/factoryreset: True # test-runner-run/run1/quiet: True # test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json -# test-runner-run/run1/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:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto +# test-runner-run/run1/script-args: --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --PICS src/app/tests/suites/certification/ci-pics-values --endpoint 1 --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto # === END CI TEST ARGUMENTS === import logging @@ -33,7 +33,7 @@ import chip.clusters as Clusters from chip.clusters import ClusterObjects as ClusterObjects from matter_testing_support import (ClusterAttributeChangeAccumulator, MatterBaseTest, TestStep, default_matter_test_main, - has_cluster, per_endpoint_test) + has_cluster, run_if_endpoint_matches) from mobly import asserts from test_plan_support import commission_if_required, if_feature_supported, read_attribute, verify_success @@ -107,7 +107,7 @@ def entry_count_verification() -> str: "The third entry in _reportedRemainingTimeValuesList_ is equal to 0") ] - @per_endpoint_test(has_cluster(Clusters.ColorControl)) + @run_if_endpoint_matches(has_cluster(Clusters.ColorControl)) async def test_TC_CC_2_2(self): gather_time = 20 diff --git a/src/python_testing/TC_LVL_2_3.py b/src/python_testing/TC_LVL_2_3.py index d45c459f1352d3..d0ac4c8c1ff893 100644 --- a/src/python_testing/TC_LVL_2_3.py +++ b/src/python_testing/TC_LVL_2_3.py @@ -24,7 +24,7 @@ # test-runner-run/run1/factoryreset: True # test-runner-run/run1/quiet: True # test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json -# test-runner-run/run1/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:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto +# test-runner-run/run1/script-args: --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --PICS src/app/tests/suites/certification/ci-pics-values --endpoint 1 --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto # === END CI TEST ARGUMENTS === import logging @@ -33,7 +33,7 @@ import chip.clusters as Clusters import test_plan_support from matter_testing_support import (ClusterAttributeChangeAccumulator, MatterBaseTest, TestStep, default_matter_test_main, - has_cluster, per_endpoint_test) + has_cluster, run_if_endpoint_matches) from mobly import asserts @@ -84,7 +84,7 @@ def steps_TC_LVL_2_3(self) -> list[TestStep]: "The third entry in reportedRemainingTimeValuesList is equal to 0") ] - @per_endpoint_test(has_cluster(Clusters.LevelControl)) + @run_if_endpoint_matches(has_cluster(Clusters.LevelControl)) async def test_TC_LVL_2_3(self): # Commissioning - already done self.step(1) diff --git a/src/python_testing/TC_SWTCH.py b/src/python_testing/TC_SWTCH.py index 3db2b213428b39..3d369be456962d 100644 --- a/src/python_testing/TC_SWTCH.py +++ b/src/python_testing/TC_SWTCH.py @@ -18,13 +18,35 @@ # for details about the block below. # # === BEGIN CI TEST ARGUMENTS === -# test-runner-runs: run1 +# test-runner-runs: run1 run2 run3 run4 +# # test-runner-run/run1/app: ${ALL_CLUSTERS_APP} # test-runner-run/run1/factoryreset: True # test-runner-run/run1/quiet: True # test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json -# test-runner-run/run1/script-args: --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto --PICS src/app/tests/suites/certification/ci-pics-values +# test-runner-run/run1/script-args: --endpoint 1 --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto --PICS src/app/tests/suites/certification/ci-pics-values +# +# test-runner-run/run2/app: ${ALL_CLUSTERS_APP} +# test-runner-run/run2/factoryreset: True +# test-runner-run/run2/quiet: True +# test-runner-run/run2/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json +# test-runner-run/run2/script-args: --endpoint 2 --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto --PICS src/app/tests/suites/certification/ci-pics-values +# +# test-runner-run/run3/app: ${ALL_CLUSTERS_APP} +# test-runner-run/run3/factoryreset: True +# test-runner-run/run3/quiet: True +# test-runner-run/run3/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json +# test-runner-run/run3/script-args: --endpoint 3 --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto --PICS src/app/tests/suites/certification/ci-pics-values +# +# test-runner-run/run4/app: ${ALL_CLUSTERS_APP} +# test-runner-run/run4/factoryreset: True +# test-runner-run/run4/quiet: True +# test-runner-run/run4/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json +# test-runner-run/run4/script-args: --endpoint 4 --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto --PICS src/app/tests/suites/certification/ci-pics-values +# # === END CI TEST ARGUMENTS === +# +# These tests run on every endpoint regardless of whether a switch is present because they are set up to auto-select. import json import logging @@ -39,7 +61,8 @@ from chip.clusters.Attribute import EventReadResult from chip.tlv import uint from matter_testing_support import (AttributeValue, ClusterAttributeChangeAccumulator, EventChangeCallback, MatterBaseTest, - TestStep, await_sequence_of_reports, default_matter_test_main, has_feature, per_endpoint_test) + TestStep, await_sequence_of_reports, default_matter_test_main, has_feature, + run_if_endpoint_matches) from mobly import asserts logger = logging.getLogger(__name__) @@ -277,7 +300,7 @@ def steps_TC_SWTCH_2_2(self): "Verify that the value is 0, and that a subscription report was received for that change."), ] - @per_endpoint_test(has_feature(Clusters.Switch, Clusters.Switch.Bitmaps.Feature.kLatchingSwitch)) + @run_if_endpoint_matches(has_feature(Clusters.Switch, Clusters.Switch.Bitmaps.Feature.kLatchingSwitch)) async def test_TC_SWTCH_2_2(self): post_prompt_settle_delay_seconds = 10.0 cluster = Clusters.Switch @@ -387,7 +410,7 @@ def steps_TC_SWTCH_2_3(self): TestStep(9, "TH reads the CurrentPosition attribute from the DUT", "Verify that the value is 0"), ] - @per_endpoint_test(has_feature(Clusters.Switch, Clusters.Switch.Bitmaps.Feature.kMomentarySwitch)) + @run_if_endpoint_matches(has_feature(Clusters.Switch, Clusters.Switch.Bitmaps.Feature.kMomentarySwitch)) async def test_TC_SWTCH_2_3(self): # Commissioning - already done self.step(1) @@ -465,7 +488,7 @@ def steps_TC_SWTCH_2_4(self): """) ] - @per_endpoint_test(has_feature(Clusters.Switch, Clusters.Switch.Bitmaps.Feature.kMomentarySwitch)) + @run_if_endpoint_matches(has_feature(Clusters.Switch, Clusters.Switch.Bitmaps.Feature.kMomentarySwitch)) async def test_TC_SWTCH_2_4(self): switch_pressed_position = self._default_pressed_position post_prompt_settle_delay_seconds = 10.0 @@ -639,7 +662,7 @@ def should_run_SWTCH_2_5(wildcard, endpoint): asf = has_feature(Clusters.Switch, Clusters.Switch.Bitmaps.Feature.kActionSwitch) return msm(wildcard, endpoint) and not asf(wildcard, endpoint) - @per_endpoint_test(should_run_SWTCH_2_5) + @run_if_endpoint_matches(should_run_SWTCH_2_5) async def test_TC_SWTCH_2_5(self): # Commissioning - already done self.step(1) @@ -818,7 +841,7 @@ def should_run_SWTCH_2_6(wildcard, endpoint): asf = has_feature(Clusters.Switch, 0x20) return msm(wildcard, endpoint) and asf(wildcard, endpoint) - @per_endpoint_test(should_run_SWTCH_2_6) + @run_if_endpoint_matches(should_run_SWTCH_2_6) async def test_TC_SWTCH_2_6(self): # Commissioning - already done self.step(1) diff --git a/src/python_testing/TC_TIMESYNC_2_1.py b/src/python_testing/TC_TIMESYNC_2_1.py index 1cfb22e17c7fc8..0f2fdba4d603f5 100644 --- a/src/python_testing/TC_TIMESYNC_2_1.py +++ b/src/python_testing/TC_TIMESYNC_2_1.py @@ -24,7 +24,7 @@ # test-runner-run/run1/factoryreset: True # test-runner-run/run1/quiet: True # test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json -# test-runner-run/run1/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:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto +# test-runner-run/run1/script-args: --endpoint 0 --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:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto # === END CI TEST ARGUMENTS === import ipaddress @@ -32,7 +32,7 @@ import chip.clusters as Clusters from chip.clusters.Types import NullValue -from matter_testing_support import (MatterBaseTest, default_matter_test_main, has_attribute, has_cluster, per_endpoint_test, +from matter_testing_support import (MatterBaseTest, default_matter_test_main, has_attribute, has_cluster, run_if_endpoint_matches, utc_time_in_matter_epoch) from mobly import asserts @@ -42,7 +42,7 @@ async def read_ts_attribute_expect_success(self, attribute): cluster = Clusters.Objects.TimeSynchronization return await self.read_single_attribute_check_success(endpoint=None, cluster=cluster, attribute=attribute) - @per_endpoint_test(has_cluster(Clusters.TimeSynchronization) and has_attribute(Clusters.TimeSynchronization.Attributes.TimeSource)) + @run_if_endpoint_matches(has_cluster(Clusters.TimeSynchronization) and has_attribute(Clusters.TimeSynchronization.Attributes.TimeSource)) async def test_TC_TIMESYNC_2_1(self): attributes = Clusters.TimeSynchronization.Attributes features = await self.read_ts_attribute_expect_success(attribute=attributes.FeatureMap) diff --git a/src/python_testing/matter_testing_support.py b/src/python_testing/matter_testing_support.py index 65baf60e51752d..7c1ac60fd3910a 100644 --- a/src/python_testing/matter_testing_support.py +++ b/src/python_testing/matter_testing_support.py @@ -625,7 +625,7 @@ class MatterTestConfig: # List of explicit tests to run by name. If empty, all tests will run tests: List[str] = field(default_factory=list) timeout: typing.Union[int, None] = None - endpoint: int = 0 + endpoint: typing.Union[int, None] = 0 app_pid: int = 0 commissioning_method: Optional[str] = None @@ -1157,7 +1157,7 @@ async def read_single_attribute_check_success( if node_id is None: node_id = self.dut_node_id if endpoint is None: - endpoint = self.matter_test_config.endpoint + endpoint = 0 if self.matter_test_config.endpoint is None else self.matter_test_config.endpoint result = await dev_ctrl.ReadAttribute(node_id, [(endpoint, attribute)], fabricFiltered=fabric_filtered) attr_ret = result[endpoint][cluster][attribute] @@ -1189,7 +1189,7 @@ async def read_single_attribute_expect_error( if node_id is None: node_id = self.dut_node_id if endpoint is None: - endpoint = self.matter_test_config.endpoint + endpoint = 0 if self.matter_test_config.endpoint is None else self.matter_test_config.endpoint result = await dev_ctrl.ReadAttribute(node_id, [(endpoint, attribute)], fabricFiltered=fabric_filtered) attr_ret = result[endpoint][cluster][attribute] @@ -1218,12 +1218,13 @@ async def write_single_attribute(self, attribute_value: object, endpoint_id: int """ dev_ctrl = self.default_controller node_id = self.dut_node_id - endpoint = self.matter_test_config.endpoint if endpoint_id is None else endpoint_id + if endpoint_id is None: + endpoint_id = 0 if self.matter_test_config.endpoint is None else self.matter_test_config.endpoint - write_result = await dev_ctrl.WriteAttribute(node_id, [(endpoint, attribute_value)]) + write_result = await dev_ctrl.WriteAttribute(node_id, [(endpoint_id, attribute_value)]) if expect_success: asserts.assert_equal(write_result[0].Status, Status.Success, - f"Expected write success for write to attribute {attribute_value} on endpoint {endpoint}") + f"Expected write success for write to attribute {attribute_value} on endpoint {endpoint_id}") return write_result[0].Status async def send_single_cmd( @@ -1236,7 +1237,7 @@ async def send_single_cmd( if node_id is None: node_id = self.dut_node_id if endpoint is None: - endpoint = self.matter_test_config.endpoint + endpoint = 0 if self.matter_test_config.endpoint is None else self.matter_test_config.endpoint result = await dev_ctrl.SendCommand(nodeid=node_id, endpoint=endpoint, payload=cmd, timedRequestTimeoutMs=timedRequestTimeoutMs, payloadCapability=payloadCapability) @@ -1821,7 +1822,7 @@ def convert_args_to_matter_config(args: argparse.Namespace) -> MatterTestConfig: config.pics = {} if args.PICS is None else read_pics_from_file(args.PICS) config.tests = [] if args.tests is None else args.tests config.timeout = args.timeout # This can be none, we pull the default from the test if it's unspecified - config.endpoint = 0 if args.endpoint is None else args.endpoint + config.endpoint = args.endpoint config.app_pid = 0 if args.app_pid is None else args.app_pid config.controller_node_id = args.controller_node_id @@ -1874,7 +1875,7 @@ def parse_matter_test_args(argv: Optional[List[str]] = None) -> MatterTestConfig metavar='NODE_ID', dest='dut_node_ids', default=[], help='Node ID for primary DUT communication, ' 'and NodeID to assign if commissioning (default: %d)' % _DEFAULT_DUT_NODE_ID, nargs="+") - basic_group.add_argument('--endpoint', type=int, default=0, help="Endpoint under test") + basic_group.add_argument('--endpoint', type=int, default=None, help="Endpoint under test") basic_group.add_argument('--app-pid', type=int, default=0, help="The PID of the app against which the test is going to run") basic_group.add_argument('--timeout', type=int, help="Test timeout in seconds") basic_group.add_argument("--PICS", help="PICS file path", type=str) @@ -1982,20 +1983,6 @@ def async_runner(self: MatterBaseTest, *args, **kwargs): return async_runner -def per_node_test(body): - """ Decorator to be used for PICS-free tests that apply to the entire node. - - Use this decorator when your script needs to be run once to validate the whole node. - To use this decorator, the test must NOT have an associated pics_ method. - """ - - def whole_node_runner(self: MatterBaseTest, *args, **kwargs): - asserts.assert_false(self.get_test_pics(self.current_test_info.name), "pics_ method supplied for per_node_test.") - return _async_runner(body, self, *args, **kwargs) - - return whole_node_runner - - EndpointCheckFunction = typing.Callable[[Clusters.Attribute.AsyncReadTransaction.ReadResponse, int], bool] @@ -2015,9 +2002,9 @@ def _has_cluster(wildcard, endpoint, cluster: ClusterObjects.Cluster) -> bool: def has_cluster(cluster: ClusterObjects.ClusterObjectDescriptor) -> EndpointCheckFunction: - """ EndpointCheckFunction that can be passed as a parameter to the per_endpoint_test decorator. + """ EndpointCheckFunction that can be passed as a parameter to the run_if_endpoint_matches decorator. - Use this function with the per_endpoint_test decorator to run this test on all endpoints with + Use this function with the run_if_endpoint_matches decorator to run this test on all endpoints with the specified cluster. For example, given a device with the following conformance EP0: cluster A, B, C @@ -2026,13 +2013,12 @@ def has_cluster(cluster: ClusterObjects.ClusterObjectDescriptor) -> EndpointChec EP3, cluster E And the following test specification: - @per_endpoint_test(has_cluster(Clusters.D)) + @run_if_endpoint_matches(has_cluster(Clusters.D)) test_mytest(self): ... - The test would be run on endpoint 1 and on endpoint 2. - - If the cluster is not found on any endpoint the decorator will call the on_skip function to + If you run this test with --endpoint 1 or --endpoint 2, the test will be run. If you run this test + with any other --endpoint the run_if_endpoint_matches decorator will call the on_skip function to notify the test harness that the test is not applicable to this node and the test will not be run. """ return partial(_has_cluster, cluster=cluster) @@ -2051,9 +2037,9 @@ def _has_attribute(wildcard, endpoint, attribute: ClusterObjects.ClusterAttribut def has_attribute(attribute: ClusterObjects.ClusterAttributeDescriptor) -> EndpointCheckFunction: - """ EndpointCheckFunction that can be passed as a parameter to the per_endpoint_test decorator. + """ EndpointCheckFunction that can be passed as a parameter to the run_if_endpoint_matches decorator. - Use this function with the per_endpoint_test decorator to run this test on all endpoints with + Use this function with the run_if_endpoint_matches decorator to run this test on all endpoints with the specified attribute. For example, given a device with the following conformance EP0: cluster A, B, C @@ -2062,13 +2048,12 @@ def has_attribute(attribute: ClusterObjects.ClusterAttributeDescriptor) -> Endpo EP3, cluster D without attribute d And the following test specification: - @per_endpoint_test(has_attribute(Clusters.D.Attributes.d)) + @run_if_endpoint_matches(has_attribute(Clusters.D.Attributes.d)) test_mytest(self): ... - The test would be run on endpoint 1 and on endpoint 2. - - If the cluster is not found on any endpoint the decorator will call the on_skip function to + If you run this test with --endpoint 1 or --endpoint 2, the test will be run. If you run this test + with any other --endpoint the run_if_endpoint_matches decorator will call the on_skip function to notify the test harness that the test is not applicable to this node and the test will not be run. """ return partial(_has_attribute, attribute=attribute) @@ -2087,9 +2072,9 @@ def _has_feature(wildcard, endpoint: int, cluster: ClusterObjects.ClusterObjectD def has_feature(cluster: ClusterObjects.ClusterObjectDescriptor, feature: IntFlag) -> EndpointCheckFunction: - """ EndpointCheckFunction that can be passed as a parameter to the per_endpoint_test decorator. + """ EndpointCheckFunction that can be passed as a parameter to the run_if_endpoint_matches decorator. - Use this function with the per_endpoint_test decorator to run this test on all endpoints with + Use this function with the run_if_endpoint_matches decorator to run this test on all endpoints with the specified feature. For example, given a device with the following conformance EP0: cluster A, B, C @@ -2098,31 +2083,69 @@ def has_feature(cluster: ClusterObjects.ClusterObjectDescriptor, feature: IntFla EP3: cluster D without feature F0 And the following test specification: - @per_endpoint_test(has_feature(Clusters.D.Bitmaps.Feature.F0)) + @run_if_endpoint_matches(has_feature(Clusters.D.Bitmaps.Feature.F0)) test_mytest(self): ... - The test would be run on endpoint 1 and on endpoint 2. - - If the cluster is not found on any endpoint the decorator will call the on_skip function to + If you run this test with --endpoint 1 or --endpoint 2, the test will be run. If you run this test + with any other --endpoint the run_if_endpoint_matches decorator will call the on_skip function to notify the test harness that the test is not applicable to this node and the test will not be run. """ return partial(_has_feature, cluster=cluster, feature=feature) -async def get_accepted_endpoints_for_test(self: MatterBaseTest, accept_function: EndpointCheckFunction) -> list[uint]: - """ Helper function for the per_endpoint_test decorator. +async def _get_all_matching_endpoints(self: MatterBaseTest, accept_function: EndpointCheckFunction) -> list[uint]: + """ Returns a list of endpoints matching the accept condition. """ + wildcard = await self.default_controller.Read(self.dut_node_id, [(Clusters.Descriptor), Attribute.AttributePath(None, None, GlobalAttributeIds.ATTRIBUTE_LIST_ID), Attribute.AttributePath(None, None, GlobalAttributeIds.FEATURE_MAP_ID), Attribute.AttributePath(None, None, GlobalAttributeIds.ACCEPTED_COMMAND_LIST_ID)]) + matching = [e for e in wildcard.attributes.keys() if accept_function(wildcard, e)] + return matching + + +async def should_run_test_on_endpoint(self: MatterBaseTest, accept_function: EndpointCheckFunction) -> bool: + """ Helper function for the run_if_endpoint_matches decorator. - Returns a list of endpoints on which the test should be run given the accept_function for the test. + Returns True if self.matter_test_config.endpoint matches the accept function. """ - wildcard = await self.default_controller.Read(self.dut_node_id, [(Clusters.Descriptor), Attribute.AttributePath(None, None, GlobalAttributeIds.ATTRIBUTE_LIST_ID), Attribute.AttributePath(None, None, GlobalAttributeIds.FEATURE_MAP_ID), Attribute.AttributePath(None, None, GlobalAttributeIds.ACCEPTED_COMMAND_LIST_ID)]) - return [e for e in wildcard.attributes.keys() if accept_function(wildcard, e)] + if self.matter_test_config.endpoint is None: + msg = """ + The --endpoint flag is required for this test. + """ + asserts.fail(msg) + matching = await (_get_all_matching_endpoints(self, accept_function)) + return self.matter_test_config.endpoint in matching + + +def run_on_singleton_matching_endpoint(accept_function: EndpointCheckFunction): + """ Test decorator for a test that needs to be run on the endpoint that matches the given accept function. + + This decorator should be used for tests where the endpoint is not known a-priori (dynamic endpoints). + Note that currently this test is limited to devices with a SINGLE matching endpoint. + """ + def run_on_singleton_matching_endpoint_internal(body): + def matching_runner(self: MatterBaseTest, *args, **kwargs): + runner_with_timeout = asyncio.wait_for(_get_all_matching_endpoints(self, accept_function), timeout=30) + matching = asyncio.run(runner_with_timeout) + asserts.assert_less_equal(len(matching), 1, "More than one matching endpoint found for singleton test.") + if not matching: + logging.info("Test is not applicable to any endpoint - skipping test") + asserts.skip('No endpoint matches test requirements') + return + # Exceptions should flow through, hence no except block + try: + old_endpoint = self.matter_test_config.endpoint + self.matter_test_config.endpoint = matching[0] + logging.info(f'Running test on endpoint {self.matter_test_config.endpoint}') + _async_runner(body, self, *args, **kwargs) + finally: + self.matter_test_config.endpoint = old_endpoint + return matching_runner + return run_on_singleton_matching_endpoint_internal -def per_endpoint_test(accept_function: EndpointCheckFunction): - """ Test decorator for a test that needs to be run once per endpoint that meets the accept_function criteria. +def run_if_endpoint_matches(accept_function: EndpointCheckFunction): + """ Test decorator for a test that needs to be run only if the endpoint meets the accept_function criteria. - Place this decorator above the test_ method to have the test framework run this test once per endpoint. + Place this decorator above the test_ method to have the test framework run this test only if the endpoint matches. This decorator takes an EndpointCheckFunction to assess whether a test needs to be run on a particular endpoint. @@ -2134,52 +2157,31 @@ def per_endpoint_test(accept_function: EndpointCheckFunction): EP3, cluster E And the following test specification: - @per_endpoint_test(has_cluster(Clusters.D)) + @run_if_endpoint_matches(has_cluster(Clusters.D)) test_mytest(self): ... - The test would be run on endpoint 1 and on endpoint 2. - - If the cluster is not found on any endpoint the decorator will call the on_skip function to + If you run this test with --endpoint 1 or --endpoint 2, the test will be run. If you run this test + with any other --endpoint the decorator will call the on_skip function to notify the test harness that the test is not applicable to this node and the test will not be run. - The decorator works by setting the self.matter_test_config.endpoint value and running the test function. - Therefore, tests that make use of this decorator should call controller functions against that endpoint. - Support functions in this file default to this endpoint. - Tests that use this decorator cannot use a pics_ method for test selection and should not reference any PICS values internally. """ - def per_endpoint_test_internal(body): + def run_if_endpoint_matches_internal(body): def per_endpoint_runner(self: MatterBaseTest, *args, **kwargs): - asserts.assert_false(self.get_test_pics(self.current_test_info.name), "pics_ method supplied for per_endpoint_test.") - runner_with_timeout = asyncio.wait_for(get_accepted_endpoints_for_test(self, accept_function), timeout=60) - endpoints = asyncio.run(runner_with_timeout) - if not endpoints: - logging.info("No matching endpoints found - skipping test") - asserts.skip('No endpoints match requirements') + asserts.assert_false(self.get_test_pics(self.current_test_info.name), + "pics_ method supplied for run_if_endpoint_matches.") + runner_with_timeout = asyncio.wait_for(should_run_test_on_endpoint(self, accept_function), timeout=60) + should_run_test = asyncio.run(runner_with_timeout) + if not should_run_test: + logging.info("Test is not applicable to this endpoint - skipping test") + asserts.skip('Endpoint does not match test requirements') return - logging.info(f"Running test on the following endpoints: {endpoints}") - # setup_class is meant to be called once, but setup_test is expected to be run before - # each iteration. Mobly will run it for us the first time, but since we're running this - # more than one time, we want to make sure we reset everything as expected. - # Ditto for teardown - we want to tear down after each iteration, and we want to notify the hook that - # the test iteration is stopped. test_stop is called by on_pass or on_fail during the last iteration or - # on failure. - original_ep = self.matter_test_config.endpoint - for e in endpoints: - logging.info(f'Running test on endpoint {e}') - if e != endpoints[0]: - self.setup_test() - self.matter_test_config.endpoint = e - _async_runner(body, self, *args, **kwargs) - if e != endpoints[-1] and not self.failed: - self.teardown_test() - test_duration = (datetime.now(timezone.utc) - self.test_start_time) / timedelta(microseconds=1) - self.runner_hook.test_stop(exception=None, duration=test_duration) - self.matter_test_config.endpoint = original_ep + logging.info(f'Running test on endpoint {self.matter_test_config.endpoint}') + _async_runner(body, self, *args, **kwargs) return per_endpoint_runner - return per_endpoint_test_internal + return run_if_endpoint_matches_internal class CommissionDeviceTest(MatterBaseTest): diff --git a/src/python_testing/test_testing/MockTestRunner.py b/src/python_testing/test_testing/MockTestRunner.py index 024228db7cd035..2d0afb8a5c2e67 100644 --- a/src/python_testing/test_testing/MockTestRunner.py +++ b/src/python_testing/test_testing/MockTestRunner.py @@ -38,13 +38,14 @@ async def __call__(self, *args, **kwargs): class MockTestRunner(): - def __init__(self, filename: str, classname: str, test: str, endpoint: int = 0, pics: dict[str, bool] = None, paa_trust_store_path=None): - self.test = test - self.endpoint = endpoint - self.pics = pics + def __init__(self, filename: str, classname: str, test: str, endpoint: int = None, pics: dict[str, bool] = None, paa_trust_store_path=None): self.kvs_storage = 'kvs_admin.json' - self.paa_path = paa_trust_store_path + self.config = MatterTestConfig(endpoint=endpoint, paa_trust_store_path=paa_trust_store_path, + pics=pics, storage_path=self.kvs_storage) self.set_test(filename, classname, test) + + self.set_test_config(self.config) + self.stack = MatterStackState(self.config) self.default_controller = self.stack.certificate_authorities[0].adminList[0].NewController( nodeId=self.config.controller_node_id, @@ -54,20 +55,16 @@ def __init__(self, filename: str, classname: str, test: str, endpoint: int = 0, def set_test(self, filename: str, classname: str, test: str): self.test = test - self.set_test_config() + self.config.tests = [self.test] module = importlib.import_module(Path(os.path.basename(filename)).stem) self.test_class = getattr(module, classname) def set_test_config(self, test_config: MatterTestConfig = MatterTestConfig()): self.config = test_config self.config.tests = [self.test] - self.config.endpoint = self.endpoint self.config.storage_path = self.kvs_storage - self.config.paa_trust_store_path = self.paa_path if not self.config.dut_node_ids: self.config.dut_node_ids = [1] - if self.pics: - self.config.pics = self.pics def Shutdown(self): self.stack.Shutdown() diff --git a/src/python_testing/test_testing/TestDecorators.py b/src/python_testing/test_testing/TestDecorators.py index 2ce418d6c5e43a..1ad5bc550bc237 100644 --- a/src/python_testing/test_testing/TestDecorators.py +++ b/src/python_testing/test_testing/TestDecorators.py @@ -32,12 +32,13 @@ from chip.clusters import Attribute try: - from matter_testing_support import (MatterBaseTest, async_test_body, get_accepted_endpoints_for_test, has_attribute, - has_cluster, has_feature, per_endpoint_test, per_node_test) + from matter_testing_support import (MatterBaseTest, MatterTestConfig, async_test_body, has_attribute, has_cluster, has_feature, + run_if_endpoint_matches, run_on_singleton_matching_endpoint, should_run_test_on_endpoint) except ImportError: sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) - from matter_testing_support import (MatterBaseTest, async_test_body, get_accepted_endpoints_for_test, has_attribute, - has_cluster, has_feature, per_endpoint_test, per_node_test) + from matter_testing_support import (MatterBaseTest, MatterTestConfig, async_test_body, has_attribute, + has_cluster, has_feature, run_if_endpoint_matches, run_on_singleton_matching_endpoint, + should_run_test_on_endpoint) from typing import Optional @@ -136,107 +137,101 @@ async def test_endpoints(self): all_endpoints = await self.default_controller.Read(self.dut_node_id, [()]) all_endpoints = list(all_endpoints.attributes.keys()) - msg = "Unexpected endpoint list returned" + msg = "Unexpected evaluation of should_run_test_on_endpoint" + for e in all_endpoints: + self.matter_test_config.endpoint = e + should_run = await should_run_test_on_endpoint(self, has_onoff) + asserts.assert_true(should_run, msg) - endpoints = await get_accepted_endpoints_for_test(self, has_onoff) - asserts.assert_equal(endpoints, all_endpoints, msg) + should_run = await should_run_test_on_endpoint(self, has_onoff_onoff) + asserts.assert_true(should_run, msg) - endpoints = await get_accepted_endpoints_for_test(self, has_onoff_onoff) - asserts.assert_equal(endpoints, all_endpoints, msg) + should_run = await should_run_test_on_endpoint(self, has_onoff_ontime) + asserts.assert_false(should_run, msg) - endpoints = await get_accepted_endpoints_for_test(self, has_onoff_ontime) - asserts.assert_equal(endpoints, [], msg) + should_run = await should_run_test_on_endpoint(self, has_timesync) + asserts.assert_false(should_run, msg) - endpoints = await get_accepted_endpoints_for_test(self, has_timesync) - asserts.assert_equal(endpoints, [], msg) - - endpoints = await get_accepted_endpoints_for_test(self, has_timesync_utc) - asserts.assert_equal(endpoints, [], msg) - - # This test should cause an assertion because it has pics_ method - @per_node_test - async def test_whole_node_with_pics(self): - pass - - # This method returns the top level pics for test_whole_node_with_pics - # It is used to test that test_whole_node_with_pics will fail since you can't have a whole node test gated on a PICS. - def pics_whole_node_with_pics(self): - return ['EXAMPLE.S'] + should_run = await should_run_test_on_endpoint(self, has_timesync_utc) + asserts.assert_false(should_run, msg) # This test should cause an assertion because it has a pics_ method - @per_endpoint_test(has_cluster(Clusters.OnOff)) - async def test_per_endpoint_with_pics(self): + @run_if_endpoint_matches(has_cluster(Clusters.OnOff)) + async def test_endpoint_with_pics(self): pass - # This method returns the top level pics for test_per_endpoint_with_pics - # It is used to test that test_per_endpoint_with_pics will fail since you can't have a per endpoint test gated on a PICS. - def pics_per_endpoint_with_pics(self): + # This method returns the top level pics for test_endpoint_with_pics + # It is used to test that test_endpoint_with_pics will fail since you can't have a per endpoint test gated on a PICS. + def pics_endpoint_with_pics(self): return ['EXAMPLE.S'] - # This test should be run once - @per_node_test - async def test_whole_node_ok(self): - pass - # This test should be run once per endpoint - @per_endpoint_test(has_cluster(Clusters.OnOff)) + @run_if_endpoint_matches(has_cluster(Clusters.OnOff)) async def test_endpoint_cluster_yes(self): pass # This test should be skipped since this cluster isn't on any endpoint - @per_endpoint_test(has_cluster(Clusters.TimeSynchronization)) + @run_if_endpoint_matches(has_cluster(Clusters.TimeSynchronization)) async def test_endpoint_cluster_no(self): pass # This test should be run once per endpoint - @per_endpoint_test(has_attribute(Clusters.OnOff.Attributes.OnOff)) + @run_if_endpoint_matches(has_attribute(Clusters.OnOff.Attributes.OnOff)) async def test_endpoint_attribute_yes(self): pass # This test should be skipped since this attribute isn't on the supported cluster - @per_endpoint_test(has_attribute(Clusters.OnOff.Attributes.OffWaitTime)) + @run_if_endpoint_matches(has_attribute(Clusters.OnOff.Attributes.OffWaitTime)) async def test_endpoint_attribute_supported_cluster_no(self): pass # This test should be skipped since this attribute is part of an unsupported cluster - @per_endpoint_test(has_attribute(Clusters.TimeSynchronization.Attributes.Granularity)) + @run_if_endpoint_matches(has_attribute(Clusters.TimeSynchronization.Attributes.Granularity)) async def test_endpoint_attribute_unsupported_cluster_no(self): pass # This test should be run once per endpoint - @per_endpoint_test(has_feature(Clusters.OnOff, Clusters.OnOff.Bitmaps.Feature.kLighting)) + @run_if_endpoint_matches(has_feature(Clusters.OnOff, Clusters.OnOff.Bitmaps.Feature.kLighting)) async def test_endpoint_feature_yes(self): pass # This test should be skipped since this attribute is part of an unsupported cluster - @per_endpoint_test(has_feature(Clusters.TimeSynchronization, Clusters.TimeSynchronization.Bitmaps.Feature.kNTPClient)) + @run_if_endpoint_matches(has_feature(Clusters.TimeSynchronization, Clusters.TimeSynchronization.Bitmaps.Feature.kNTPClient)) async def test_endpoint_feature_unsupported_cluster_no(self): pass # This test should be run since both are present - @per_endpoint_test(has_attribute(Clusters.OnOff.Attributes.OnOff) and has_cluster(Clusters.OnOff)) + @run_if_endpoint_matches(has_attribute(Clusters.OnOff.Attributes.OnOff) and has_cluster(Clusters.OnOff)) async def test_endpoint_boolean_yes(self): pass # This test should be skipped since we have an OnOff cluster, but no Time sync - @per_endpoint_test(has_cluster(Clusters.OnOff) and has_cluster(Clusters.TimeSynchronization)) + @run_if_endpoint_matches(has_cluster(Clusters.OnOff) and has_cluster(Clusters.TimeSynchronization)) async def test_endpoint_boolean_no(self): pass - @per_endpoint_test(has_cluster(Clusters.OnOff)) + @run_if_endpoint_matches(has_cluster(Clusters.OnOff)) async def test_fail_on_ep0(self): if self.matter_test_config.endpoint == 0: asserts.fail("Expected failure") - @per_endpoint_test(has_cluster(Clusters.OnOff)) + @run_if_endpoint_matches(has_cluster(Clusters.OnOff)) async def test_fail_on_ep1(self): if self.matter_test_config.endpoint == 1: asserts.fail("Expected failure") - @per_node_test - async def test_fail_on_whole_node(self): + @run_on_singleton_matching_endpoint(has_cluster(Clusters.OnOff)) + async def test_run_on_singleton_matching_endpoint(self): + pass + + @run_on_singleton_matching_endpoint(has_cluster(Clusters.OnOff)) + async def test_run_on_singleton_matching_endpoint_failure(self): asserts.fail("Expected failure") + @run_on_singleton_matching_endpoint(has_attribute(Clusters.OnOff.Attributes.OffWaitTime)) + async def test_no_run_on_singleton_matching_endpoint(self): + pass + def main(): failures = [] @@ -258,82 +253,129 @@ def main(): if not ok: failures.append("Test case failure: test_endpoints") - test_name = 'test_whole_node_with_pics' - test_runner.set_test('TestDecorators.py', 'TestDecorators', test_name) - ok = test_runner.run_test_with_mock_read(read_resp, hooks) - if ok: - failures.append(f"Did not get expected test assertion on {test_name}") - - test_name = 'test_per_endpoint_with_pics' + test_name = 'test_endpoint_with_pics' test_runner.set_test('TestDecorators.py', 'TestDecorators', test_name) ok = test_runner.run_test_with_mock_read(read_resp, hooks) if ok: failures.append(f"Did not get expected test assertion on {test_name}") # Test should run once for the whole node, regardless of the number of endpoints - def run_check(test_name: str, read_response: Attribute.AsyncReadTransaction.ReadResponse, expected_runs: int, expect_skip: bool) -> None: + def run_check(test_name: str, read_response: Attribute.AsyncReadTransaction.ReadResponse, expect_skip: bool) -> None: nonlocal failures test_runner.set_test('TestDecorators.py', 'TestDecorators', test_name) hooks = DecoratorTestRunnerHooks() - ok = test_runner.run_test_with_mock_read(read_response, hooks) - started_ok = len(hooks.started) == expected_runs - skipped_ok = (hooks.skipped != []) == expect_skip - stopped_ok = hooks.stopped == expected_runs + num_endpoints = 2 + for e in [0, 1]: + test_runner.set_test_config(MatterTestConfig(endpoint=e)) + ok = test_runner.run_test_with_mock_read(read_response, hooks) + started_ok = len(hooks.started) == num_endpoints + expected_num_skips = 2 if expect_skip else 0 + skipped_ok = len(hooks.skipped) == expected_num_skips + stopped_ok = hooks.stopped == num_endpoints if not ok or not started_ok or not skipped_ok or not stopped_ok: failures.append( - f'Expected {expected_runs} run of {test_name}, skips expected: {expect_skip}. Runs: {hooks.started}, skips: {hooks.skipped} stops: {hooks.stopped}') - - def check_once_per_node(test_name: str): - run_check(test_name, get_clusters([0]), 1, False) - run_check(test_name, get_clusters([0, 1]), 1, False) + f'Expected {num_endpoints} run of {test_name}, skips expected: {expect_skip}. Runs: {hooks.started}, skips: {hooks.skipped} stops: {hooks.stopped}') def check_once_per_endpoint(test_name: str): - run_check(test_name, get_clusters([0]), 1, False) - run_check(test_name, get_clusters([0, 1]), 2, False) + run_check(test_name, get_clusters([0, 1]), False) - def check_skipped(test_name: str): - run_check(test_name, get_clusters([0]), 1, True) - run_check(test_name, get_clusters([0, 1]), 1, True) + def check_all_skipped(test_name: str): + run_check(test_name, get_clusters([0, 1]), True) - check_once_per_node('test_whole_node_ok') check_once_per_endpoint('test_endpoint_cluster_yes') - check_skipped('test_endpoint_cluster_no') + check_all_skipped('test_endpoint_cluster_no') check_once_per_endpoint('test_endpoint_attribute_yes') - check_skipped('test_endpoint_attribute_supported_cluster_no') - check_skipped('test_endpoint_attribute_unsupported_cluster_no') + check_all_skipped('test_endpoint_attribute_supported_cluster_no') + check_all_skipped('test_endpoint_attribute_unsupported_cluster_no') check_once_per_endpoint('test_endpoint_feature_yes') - check_skipped('test_endpoint_feature_unsupported_cluster_no') + check_all_skipped('test_endpoint_feature_unsupported_cluster_no') check_once_per_endpoint('test_endpoint_boolean_yes') - check_skipped('test_endpoint_boolean_no') + check_all_skipped('test_endpoint_boolean_no') test_name = 'test_fail_on_ep0' test_runner.set_test('TestDecorators.py', 'TestDecorators', test_name) read_resp = get_clusters([0, 1]) + # fail on EP0, pass on EP1 + test_runner.set_test_config(MatterTestConfig(endpoint=0)) ok = test_runner.run_test_with_mock_read(read_resp, hooks) if ok: failures.append(f"Did not get expected test assertion on {test_name}") - - test_name = 'test_fail_on_ep1' - test_runner.set_test('TestDecorators.py', 'TestDecorators', test_name) - read_resp = get_clusters([0, 1]) + test_runner.set_test_config(MatterTestConfig(endpoint=1)) ok = test_runner.run_test_with_mock_read(read_resp, hooks) - if ok: - failures.append(f"Did not get expected test assertion on {test_name}") + if not ok: + failures.append(f"Unexpected failure on {test_name}") test_name = 'test_fail_on_ep1' test_runner.set_test('TestDecorators.py', 'TestDecorators', test_name) - read_resp = get_clusters([0]) + read_resp = get_clusters([0, 1]) + # pass on EP0, fail on EP1 + test_runner.set_test_config(MatterTestConfig(endpoint=0)) ok = test_runner.run_test_with_mock_read(read_resp, hooks) if not ok: failures.append(f"Unexpected failure on {test_name}") - - test_name = 'test_fail_on_whole_node' - test_runner.set_test('TestDecorators.py', 'TestDecorators', test_name) - read_resp = get_clusters([0, 1]) + test_runner.set_test_config(MatterTestConfig(endpoint=1)) ok = test_runner.run_test_with_mock_read(read_resp, hooks) if ok: failures.append(f"Did not get expected test assertion on {test_name}") + def run_singleton_dynamic(test_name: str, cluster_list: list[int]) -> tuple[bool, DecoratorTestRunnerHooks]: + nonlocal failures + read_resp = get_clusters(cluster_list) + test_runner.set_test('TestDecorators.py', 'TestDecorators', test_name) + test_runner.set_test_config(MatterTestConfig(endpoint=2)) + hooks = DecoratorTestRunnerHooks() + ok = test_runner.run_test_with_mock_read(read_resp, hooks) + # for all tests, we need to ensure the endpoint was set back to the prior values + if test_runner.config.endpoint != 2: + failures.append(f"Dynamic tests {test_name} with clusters {cluster_list} did not set endpoint back to prior") + # All tests should have a start and a stop + started_ok = len(hooks.started) == 1 + stopped_ok = hooks.stopped == 1 + if not started_ok or not stopped_ok: + failures.append( + f'Hooks failure on {test_name}, Runs: {hooks.started}, skips: {hooks.skipped} stops: {hooks.stopped}') + return ok, hooks + + def expect_success_dynamic(test_name: str, cluster_list: list[int]): + ok, hooks = run_singleton_dynamic(test_name, cluster_list) + if not ok: + failures.append(f"Unexpected failure on {test_name} with cluster list {cluster_list}") + if hooks.skipped: + failures.append(f'Unexpected skip call on {test_name} with cluster list {cluster_list}') + + def expect_failure_dynamic(test_name: str, cluster_list: list[int]): + ok, hooks = run_singleton_dynamic(test_name, cluster_list) + if ok: + failures.append(f"Unexpected success on {test_name} with cluster list {cluster_list}") + if hooks.skipped: + # We don't expect a skip call because the test actually failed. + failures.append(f'Skip called for {test_name} with cluster list {cluster_list}') + + def expect_skip_dynamic(test_name: str, cluster_list: list[int]): + ok, hooks = run_singleton_dynamic(test_name, cluster_list) + if not ok: + failures.append(f"Unexpected failure on {test_name} with cluster list {cluster_list}") + if not hooks.skipped: + # We don't expect a skip call because the test actually failed. + failures.append(f'Skip not called for {test_name} with cluster list {cluster_list}') + + test_name = 'test_run_on_singleton_matching_endpoint' + expect_success_dynamic(test_name, [0]) + expect_success_dynamic(test_name, [1]) + # expect failure because there is more than 1 endpoint + expect_failure_dynamic(test_name, [0, 1]) + + test_name = 'test_run_on_singleton_matching_endpoint_failure' + expect_failure_dynamic(test_name, [0]) + expect_failure_dynamic(test_name, [1]) + expect_failure_dynamic(test_name, [0, 1]) + + test_name = 'test_no_run_on_singleton_matching_endpoint' + # no failure, no matches, expect skips on all endpoints + expect_skip_dynamic(test_name, [0]) + expect_skip_dynamic(test_name, [1]) + expect_skip_dynamic(test_name, [0, 1]) + test_runner.Shutdown() print( f"Test of Decorators: test response incorrect: {len(failures)}") diff --git a/src/python_testing/test_testing/test_TC_CCNTL_2_2.py b/src/python_testing/test_testing/test_TC_CCNTL_2_2.py index 77971779fecbfc..d528b5652e1b45 100644 --- a/src/python_testing/test_testing/test_TC_CCNTL_2_2.py +++ b/src/python_testing/test_testing/test_TC_CCNTL_2_2.py @@ -178,7 +178,7 @@ def main(th_server_app: str): print(f'paa = {paa_path}') pics = {"PICS_SDK_CI_ONLY": True} - test_runner = MyMock('TC_CCTRL_2_2', 'TC_CCTRL_2_2', 'test_TC_CCTRL_2_2', 1, paa_trust_store_path=paa_path, pics=pics) + test_runner = MyMock('TC_CCTRL_2_2', 'TC_CCTRL_2_2', 'test_TC_CCTRL_2_2', paa_trust_store_path=paa_path, pics=pics) config = MatterTestConfig() config.global_test_params = {'th_server_app_path': th_server_app} test_runner.set_test_config(config) diff --git a/src/python_testing/test_testing/test_TC_MCORE_FS_1_1.py b/src/python_testing/test_testing/test_TC_MCORE_FS_1_1.py index ec91db72551523..5b2e29b71915b1 100644 --- a/src/python_testing/test_testing/test_TC_MCORE_FS_1_1.py +++ b/src/python_testing/test_testing/test_TC_MCORE_FS_1_1.py @@ -149,7 +149,7 @@ def main(th_server_app: str): print(f'paa = {paa_path}') pics = {"PICS_SDK_CI_ONLY": True} - test_runner = MyMock('TC_MCORE_FS_1_1', 'TC_MCORE_FS_1_1', 'test_TC_MCORE_FS_1_1', 1, paa_trust_store_path=paa_path, pics=pics) + test_runner = MyMock('TC_MCORE_FS_1_1', 'TC_MCORE_FS_1_1', 'test_TC_MCORE_FS_1_1', paa_trust_store_path=paa_path, pics=pics) config = MatterTestConfig() config.user_params = {'th_server_app_path': th_server_app} test_runner.set_test_config(config) From ee40a181d407cabe738b4e454ad55b8100cf42cc Mon Sep 17 00:00:00 2001 From: Saravana Perumal K <104007654+Saravana-kr22@users.noreply.github.com> Date: Wed, 4 Sep 2024 03:10:13 +0530 Subject: [PATCH 05/15] Updated yaml script as per the SQA issues (#35253) * Updated yaml script as the SQA issues * Restyled by whitespace * Restyled by prettier-yaml * Updated NumberOfHolidaySchedulesSupportedValue as per test plan * Restyled by whitespace * Updated DRLK-2.6 yaml * Updated DRlk Yaml as per the comments --------- Co-authored-by: Restyled.io --- .../certification/Test_TC_DRLK_2_10.yaml | 333 +++++++--------- .../certification/Test_TC_DRLK_2_11.yaml | 76 ++-- .../certification/Test_TC_DRLK_2_6.yaml | 36 +- .../certification/Test_TC_DRLK_2_7.yaml | 113 ++---- .../certification/Test_TC_DRLK_2_8.yaml | 10 + .../suites/certification/Test_TC_IDM_3_2.yaml | 47 ++- .../suites/certification/Test_TC_IDM_5_2.yaml | 68 ++-- .../suites/certification/Test_TC_IDM_6_1.yaml | 3 +- .../suites/certification/Test_TC_LVL_8_1.yaml | 361 ++++++++++++++++++ .../suites/certification/Test_TC_LVL_9_1.yaml | 26 +- 10 files changed, 674 insertions(+), 399 deletions(-) create mode 100644 src/app/tests/suites/certification/Test_TC_LVL_8_1.yaml diff --git a/src/app/tests/suites/certification/Test_TC_DRLK_2_10.yaml b/src/app/tests/suites/certification/Test_TC_DRLK_2_10.yaml index 321e04f55f3e2a..d608353dd5393b 100644 --- a/src/app/tests/suites/certification/Test_TC_DRLK_2_10.yaml +++ b/src/app/tests/suites/certification/Test_TC_DRLK_2_10.yaml @@ -34,24 +34,14 @@ tests: 2. Build respective app (lock-app) 3. Commission DUT to TH 4. Open 2nd terminal of DUT and provide the below command to obtain PID of DUT - ps -aef|grep lock-app + ps -aef|grep lock-app 5. Follow the Verification step below to generate the event in 2nd terminal of DUT - Pre-Conditions "1 TH is commissioned with the DUT 2 Lock device is the DUT - Before sending the Events proceed following steps: - - 1. Send Set User Command and Get User for setting User. - - 2. Send Set Credential Command and Get Credential Status for setting PIN code. - - After sending Events with all condition proceed following step - - 1. Send Clear Credential and Clear User Command." disabled: true - label: @@ -62,9 +52,10 @@ tests: To trigger the event give the below command by opening an another terminal in DUT (Below is the example command developed in lock-app to generate the event, Vendor Dut should have capability to generate this event) echo '{"Cmd": "SendDoorLockAlarm", "Params": { "EndpointId": 1, "AlarmCode": 0 } }' > /tmp/chip_lock_app_fifo- (PID of lock-app) + For example : - echo '{"Cmd": "SendDoorLockAlarm", "Params": { "EndpointId": 1, "AlarmCode": 0 } }' > /tmp/chip_lock_app_fifo-3940 (PID may vary based on the actual DUT) + echo '{"Cmd": "SendDoorLockAlarm", "Params": { "EndpointId": 1, "AlarmCode": 0 } }' > /tmp/chip_lock_app_fifo-3940 (PID may vary based on the actual DUT) disabled: true - label: "Step 1b: TH reads the DoorLockAlarm event from DUT" @@ -82,6 +73,7 @@ tests: [1659521453.110507][4098:4103] CHIP:TOO: DoorLockAlarm: { [1659521453.110557][4098:4103] CHIP:TOO: AlarmCode: 0 [1659521453.110591][4098:4103] CHIP:TOO: } + disabled: true - label: "Step 2a: Trigger the DUT to generate DoorStateChange Event" @@ -109,16 +101,57 @@ tests: disabled: true - label: - "Step 3a: TH sends the Lock Door command (using Remote) to the DUT - with valid PINCode" - PICS: DRLK.S.C00.Rsp + "Step 2c: TH sends SetUser Command to DUT with the following values: + a) OperationType as 0, b)UserIndex as 1, c)UserName as xxx, + d)UserUniqueID as 6452, e)UserStatus as 1, f)UserType as 0, + g)CredentialRule as 0" + PICS: DRLK.S.F08 && DRLK.S.C1a.Rsp verification: | ./chip-tool doorlock set-user 0 1 xxx 6452 1 0 0 1 1 --timedInteractionTimeoutMs 1000 Via the TH (chip-tool), verify the SUCCESS response for setting the Users details. - [1656497453.684077][25847:25853] CHIP:DMG: status = 0x00 (SUCCESS), + [1721036387.039] [100957:100959] [DMG] StatusIB = + [1721036387.039] [100957:100959] [DMG] { + [1721036387.039] [100957:100959] [DMG] status = 0x00 (SUCCESS), + [1721036387.040] [100957:100959] [DMG] }, + disabled: true + + - label: + "Step 2d: TH reads MinPINCodeLength attribute and saves the value as + min_pin_code_length." + PICS: DRLK.S.F08 && DRLK.S.F00 + verification: | + ./chip-tool doorlock read min-pincode-length 1 1 + + Via the TH (chip-tool), verify that the MinPINCodeLength attribute contains value in the range of 0 to 255. + + [1654680280.327488][3639:3644] CHIP:TOO: Endpoint: 1 Cluster: 0x0000_0101 Attribute 0x0000_0018 DataVersion: 3738767914 + [1654680280.327558][3639:3644] CHIP:TOO: MinPINCodeLength: 6 + disabled: true + + - label: + "Step 2e: TH reads MaxPINCodeLength attribute and saves the value as + max_pin_code_length" + PICS: DRLK.S.F08 && DRLK.S.F00 + verification: | + ./chip-tool doorlock read max-pincode-length 1 1 + Via the TH (chip-tool), verify that the MaxPINCodeLength attribute contains value in the range of 0 to 255. + + [1654680165.815239][3630:3635] CHIP:TOO: Endpoint: 1 Cluster: 0x0000_0101 Attribute 0x0000_0017 DataVersion: 3738767914 + [1654680165.815374][3630:3635] CHIP:TOO: MaxPINCodeLength: 8 + disabled: true + + - label: + "Step 2f: TH sends SetCredential Command to DUT with the following + fields and CredentialData Length is in an inclusive range of + min_pin_code_length to max_pin_code_length, CredentialData='123456' a) + OperationType as 0, b) Credential as 1 2, c) CredentialData as 123456, + d) UserIndex as 1, e) UserStatus as null f) UserType as null. Save + CredentialData as pin_code" + PICS: DRLK.S.F08 && DRLK.S.F00 && DRLK.S.C22.Rsp && DRLK.S.C23.Tx + verification: | ./chip-tool doorlock set-credential 0 '{ "credentialType" : 1 , "credentialIndex" : 2 }' 123456 1 null null 1 1 --timedInteractionTimeoutMs 1000 Via the TH (chip-tool), verify the SUCCESS response for setting the credential details. @@ -127,7 +160,13 @@ tests: [1656497508.814257][25858:25863] CHIP:TOO: status: 0 [1656497508.814301][25858:25863] CHIP:TOO: userIndex: null [1656497508.814343][25858:25863] CHIP:TOO: nextCredentialIndex: 3 + disabled: true + - label: + "Step 3a: TH sends the Lock Door command (using Remote) to the DUT + with valid PINCode" + PICS: DRLK.S.C00.Rsp + verification: | ./chip-tool doorlock lock-door 1 1 --timedInteractionTimeoutMs 1000 --PINCode 123456 Via the TH (chip-tool), verify the SUCCESS response for door lock operation with valid PINCode. @@ -137,15 +176,6 @@ tests: [1654687870.020756][4246:4251] CHIP:DMG: { [1654687870.020797][4246:4251] CHIP:DMG: status = 0x00 (SUCCESS), [1654687870.020837][4246:4251] CHIP:DMG: }, - [1654687870.020879][4246:4251] CHIP:DMG: - [1654687870.020919][4246:4251] CHIP:DMG: }, - [1654687870.020963][4246:4251] CHIP:DMG: - [1654687870.020994][4246:4251] CHIP:DMG: }, - [1654687870.021033][4246:4251] CHIP:DMG: - [1654687870.021063][4246:4251] CHIP:DMG: ], - [1654687870.021100][4246:4251] CHIP:DMG: - [1654687870.021130][4246:4251] CHIP:DMG: InteractionModelRevision = 1 - [1654687870.021160][4246:4251] CHIP:DMG: }," disabled: true - label: "Step 3b: TH reads the LockOperation event from DUT" @@ -192,16 +222,6 @@ tests: [1659777464.384997][3157:3162] CHIP:DMG: { [1659777464.385032][3157:3162] CHIP:DMG: status = 0x00 (SUCCESS), [1659777464.385067][3157:3162] CHIP:DMG: }, - [1659777464.385099][3157:3162] CHIP:DMG: - [1659777464.385128][3157:3162] CHIP:DMG: }, - [1659777464.385162][3157:3162] CHIP:DMG: - [1659777464.385189][3157:3162] CHIP:DMG: }, - [1659777464.385221][3157:3162] CHIP:DMG: - [1659777464.385244][3157:3162] CHIP:DMG: ], - [1659777464.385272][3157:3162] CHIP:DMG: - [1659777464.385295][3157:3162] CHIP:DMG: InteractionModelRevision = 1 - [1659777464.385318][3157:3162] CHIP:DMG: }, - [1659777464.385375][3157:3162] CHIP:DMG: Received Command Response Status for Endpoint=1 Cluster=0x0000_0101 Command=0x0000_0001 Status=0x0 disabled: true - label: "Step 3d: TH reads the LockOperation event from DUT" @@ -338,17 +358,6 @@ tests: [1659777735.863849][3232:3237] CHIP:DMG: { [1659777735.863888][3232:3237] CHIP:DMG: status = 0x01 (FAILURE), [1659777735.863925][3232:3237] CHIP:DMG: }, - [1659777735.863962][3232:3237] CHIP:DMG: - [1659777735.863996][3232:3237] CHIP:DMG: }, - [1659777735.864038][3232:3237] CHIP:DMG: - [1659777735.864068][3232:3237] CHIP:DMG: }, - [1659777735.864104][3232:3237] CHIP:DMG: - [1659777735.864133][3232:3237] CHIP:DMG: ], - [1659777735.864166][3232:3237] CHIP:DMG: - [1659777735.864192][3232:3237] CHIP:DMG: InteractionModelRevision = 1 - [1659777735.864218][3232:3237] CHIP:DMG: }, - [1659777735.864281][3232:3237] CHIP:DMG: Received Command Response Status for Endpoint=1 Cluster=0x0000_0101 Command=0x0000_0000 Status=0x1 - [1659777735.864317][3232:3237] CHIP:TOO: Error: IM Error 0x00000501: General error: 0x01 (FAILURE) disabled: true - label: "Step 4b: TH reads the LockOperationError event from DUT" @@ -356,26 +365,26 @@ tests: verification: | ./chip-tool doorlock read-event lock-operation-error 1 1 - Via the TH (chip-tool), verify that the: - -LockOperationType value as 0(Lock). - -OperationSource value as 7(Remote). - -OperationError is set to 1(InvalidCredential). - -Priority is set to Critical. - - [1659777833.226970][3243:3248] CHIP:DMG: } - [1659777833.227194][3243:3248] CHIP:TOO: Endpoint: 1 Cluster: 0x0000_0101 Event 0x0000_0003 - [1659777833.227221][3243:3248] CHIP:TOO: Event number: 8 - [1659777833.227243][3243:3248] CHIP:TOO: Priority: Critical - [1659777833.227264][3243:3248] CHIP:TOO: Timestamp: 3439177 - [1659777833.227367][3243:3248] CHIP:TOO: LockOperationError: { - [1659777833.227407][3243:3248] CHIP:TOO: LockOperationType: 0 - [1659777833.227431][3243:3248] CHIP:TOO: OperationSource: 7 - [1659777833.227453][3243:3248] CHIP:TOO: OperationError: 1 - [1659777833.227476][3243:3248] CHIP:TOO: UserIndex: null - [1659777833.227498][3243:3248] CHIP:TOO: FabricIndex: 1 - [1659777833.227523][3243:3248] CHIP:TOO: SourceNode: 112233 - [1659777833.227553][3243:3248] CHIP:TOO: Credentials: null - [1659777833.227696][3243:3248] CHIP:TOO: } + Via the TH (chip-tool), verify that the: + -LockOperationType value as 0(Lock). + -OperationSource value as 7(Remote). + -OperationError is set to 1(InvalidCredential). + -Priority is set to Critical. + + [1659777833.226970][3243:3248] CHIP:DMG: } + [1659777833.227194][3243:3248] CHIP:TOO: Endpoint: 1 Cluster: 0x0000_0101 Event 0x0000_0003 + [1659777833.227221][3243:3248] CHIP:TOO: Event number: 8 + [1659777833.227243][3243:3248] CHIP:TOO: Priority: Critical + [1659777833.227264][3243:3248] CHIP:TOO: Timestamp: 3439177 + [1659777833.227367][3243:3248] CHIP:TOO: LockOperationError: { + [1659777833.227407][3243:3248] CHIP:TOO: LockOperationType: 0 + [1659777833.227431][3243:3248] CHIP:TOO: OperationSource: 7 + [1659777833.227453][3243:3248] CHIP:TOO: OperationError: 1 + [1659777833.227476][3243:3248] CHIP:TOO: UserIndex: null + [1659777833.227498][3243:3248] CHIP:TOO: FabricIndex: 1 + [1659777833.227523][3243:3248] CHIP:TOO: SourceNode: 112233 + [1659777833.227553][3243:3248] CHIP:TOO: Credentials: null + [1659777833.227696][3243:3248] CHIP:TOO: } disabled: true - label: @@ -384,23 +393,13 @@ tests: verification: | ./chip-tool doorlock unlock-door 1 1 --timedInteractionTimeoutMs 1000 --PINCode 12345678 - Via the TH (chip-tool), verify the FAILURE response for door unlock operation with invalid PINCode. + Via the TH (chip-tool), verify the FAILURE response for door unlock operation with invalid PINCode. - [1659777885.573854][3251:3256] CHIP:DMG: StatusIB = - [1659777885.573896][3251:3256] CHIP:DMG: { - [1659777885.573938][3251:3256] CHIP:DMG: status = 0x01 (FAILURE), - [1659777885.573981][3251:3256] CHIP:DMG: }, - [1659777885.574025][3251:3256] CHIP:DMG: - [1659777885.574064][3251:3256] CHIP:DMG: }, - [1659777885.574105][3251:3256] CHIP:DMG: - [1659777885.574138][3251:3256] CHIP:DMG: }, - [1659777885.574177][3251:3256] CHIP:DMG: - [1659777885.574205][3251:3256] CHIP:DMG: ], - [1659777885.574240][3251:3256] CHIP:DMG: - [1659777885.574268][3251:3256] CHIP:DMG: InteractionModelRevision = 1 - [1659777885.574296][3251:3256] CHIP:DMG: }, - [1659777885.574366][3251:3256] CHIP:DMG: Received Command Response Status for Endpoint=1 Cluster=0x0000_0101 Command=0x0000_0001 Status=0x1 - [1659777885.574405][3251:3256] CHIP:TOO: Error: IM Error 0x00000501: General error: 0x01 (FAILURE) + [1659777885.573854][3251:3256] CHIP:DMG: StatusIB = + [1659777885.573896][3251:3256] CHIP:DMG: { + [1659777885.573938][3251:3256] CHIP:DMG: status = 0x01 (FAILURE), + [1659777885.573981][3251:3256] CHIP:DMG: }, + [1659777885.574025][3251:3256] CHIP:DMG: disabled: true - label: "Step 4d: TH reads the LockOperationError event from DUT" @@ -464,7 +463,7 @@ tests: - label: "Step 5b: TH reads the LockUserChange event from DUT" PICS: DRLK.S.E04 verification: | - ./chip-tool doorlock read-event lock-user-change 1 1 + ./chip-tool doorlock read-event lock-user-change 1 1 Via the TH (chip-tool), verify that the: -LockDataType is set to 2 (UserIndex). @@ -474,44 +473,44 @@ tests: [1659778039.468487][3278:3283] CHIP:DMG: } [1659778039.468725][3278:3283] CHIP:TOO: Endpoint: 1 Cluster: 0x0000_0101 Event 0x0000_0004 - [1659778039.468757][3278:3283] CHIP:TOO: Event number: 3 - [1659778039.468783][3278:3283] CHIP:TOO: Priority: Info - [1659778039.468808][3278:3283] CHIP:TOO: Timestamp: 3309634 - [1659778039.468915][3278:3283] CHIP:TOO: LockUserChange: { - [1659778039.468956][3278:3283] CHIP:TOO: LockDataType: 2 - [1659778039.468984][3278:3283] CHIP:TOO: DataOperationType: 0 - [1659778039.469010][3278:3283] CHIP:TOO: OperationSource: 7 - [1659778039.469036][3278:3283] CHIP:TOO: UserIndex: 1 - [1659778039.469061][3278:3283] CHIP:TOO: FabricIndex: 1 - [1659778039.469088][3278:3283] CHIP:TOO: SourceNode: 112233 - [1659778039.469114][3278:3283] CHIP:TOO: DataIndex: 1 - [1659778039.469137][3278:3283] CHIP:TOO: } + [1659778039.468757][3278:3283] CHIP:TOO: Event number: 3 + [1659778039.468783][3278:3283] CHIP:TOO: Priority: Info + [1659778039.468808][3278:3283] CHIP:TOO: Timestamp: 3309634 + [1659778039.468915][3278:3283] CHIP:TOO: LockUserChange: { + [1659778039.468956][3278:3283] CHIP:TOO: LockDataType: 2 + [1659778039.468984][3278:3283] CHIP:TOO: DataOperationType: 0 + [1659778039.469010][3278:3283] CHIP:TOO: OperationSource: 7 + [1659778039.469036][3278:3283] CHIP:TOO: UserIndex: 1 + [1659778039.469061][3278:3283] CHIP:TOO: FabricIndex: 1 + [1659778039.469088][3278:3283] CHIP:TOO: SourceNode: 112233 + [1659778039.469114][3278:3283] CHIP:TOO: DataIndex: 1 + [1659778039.469137][3278:3283] CHIP:TOO: } [1659778039.469250][3278:3283] CHIP:TOO: Endpoint: 1 Cluster: 0x0000_0101 Event 0x0000_0004 - [1659778039.469320][3278:3283] CHIP:TOO: Event number: 4 - [1659778039.469344][3278:3283] CHIP:TOO: Priority: Info - [1659778039.469369][3278:3283] CHIP:TOO: Timestamp: 3318674 - [1659778039.469429][3278:3283] CHIP:TOO: LockUserChange: { - [1659778039.469457][3278:3283] CHIP:TOO: LockDataType: 6 - [1659778039.469483][3278:3283] CHIP:TOO: DataOperationType: 0 - [1659778039.469508][3278:3283] CHIP:TOO: OperationSource: 7 - [1659778039.469532][3278:3283] CHIP:TOO: UserIndex: 1 - [1659778039.469557][3278:3283] CHIP:TOO: FabricIndex: 1 - [1659778039.469582][3278:3283] CHIP:TOO: SourceNode: 112233 - [1659778039.469607][3278:3283] CHIP:TOO: DataIndex: 1 - [1659778039.469632][3278:3283] CHIP:TOO: } + [1659778039.469320][3278:3283] CHIP:TOO: Event number: 4 + [1659778039.469344][3278:3283] CHIP:TOO: Priority: Info + [1659778039.469369][3278:3283] CHIP:TOO: Timestamp: 3318674 + [1659778039.469429][3278:3283] CHIP:TOO: LockUserChange: { + [1659778039.469457][3278:3283] CHIP:TOO: LockDataType: 6 + [1659778039.469483][3278:3283] CHIP:TOO: DataOperationType: 0 + [1659778039.469508][3278:3283] CHIP:TOO: OperationSource: 7 + [1659778039.469532][3278:3283] CHIP:TOO: UserIndex: 1 + [1659778039.469557][3278:3283] CHIP:TOO: FabricIndex: 1 + [1659778039.469582][3278:3283] CHIP:TOO: SourceNode: 112233 + [1659778039.469607][3278:3283] CHIP:TOO: DataIndex: 1 + [1659778039.469632][3278:3283] CHIP:TOO: } [1659778039.469739][3278:3283] CHIP:TOO: Endpoint: 1 Cluster: 0x0000_0101 Event 0x0000_0004 - [1659778039.469765][3278:3283] CHIP:TOO: Event number: 10 - [1659778039.469790][3278:3283] CHIP:TOO: Priority: Info - [1659778039.469815][3278:3283] CHIP:TOO: Timestamp: 3692303 - [1659778039.469851][3278:3283] CHIP:TOO: LockUserChange: { - [1659778039.469878][3278:3283] CHIP:TOO: LockDataType: 2 - [1659778039.469903][3278:3283] CHIP:TOO: DataOperationType: 0 - [1659778039.469981][3278:3283] CHIP:TOO: OperationSource: 7 - [1659778039.470006][3278:3283] CHIP:TOO: UserIndex: 2 - [1659778039.470031][3278:3283] CHIP:TOO: FabricIndex: 1 - [1659778039.470056][3278:3283] CHIP:TOO: SourceNode: 112233 - [1659778039.470081][3278:3283] CHIP:TOO: DataIndex: 4 - [1659778039.470104][3278:3283] CHIP:TOO: } + [1659778039.469765][3278:3283] CHIP:TOO: Event number: 10 + [1659778039.469790][3278:3283] CHIP:TOO: Priority: Info + [1659778039.469815][3278:3283] CHIP:TOO: Timestamp: 3692303 + [1659778039.469851][3278:3283] CHIP:TOO: LockUserChange: { + [1659778039.469878][3278:3283] CHIP:TOO: LockDataType: 2 + [1659778039.469903][3278:3283] CHIP:TOO: DataOperationType: 0 + [1659778039.469981][3278:3283] CHIP:TOO: OperationSource: 7 + [1659778039.470006][3278:3283] CHIP:TOO: UserIndex: 2 + [1659778039.470031][3278:3283] CHIP:TOO: FabricIndex: 1 + [1659778039.470056][3278:3283] CHIP:TOO: SourceNode: 112233 + [1659778039.470081][3278:3283] CHIP:TOO: DataIndex: 4 + [1659778039.470104][3278:3283] CHIP:TOO: } disabled: true - label: @@ -528,15 +527,6 @@ tests: [1658142169.347900][2900:2905] CHIP:DMG: { [1658142169.347945][2900:2905] CHIP:DMG: status = 0x00 (SUCCESS), [1658142169.347986][2900:2905] CHIP:DMG: }, - [1658142169.348030][2900:2905] CHIP:DMG: - [1658142169.348066][2900:2905] CHIP:DMG: }, - [1658142169.348112][2900:2905] CHIP:DMG: - [1658142169.348146][2900:2905] CHIP:DMG: }, - [1658142169.348195][2900:2905] CHIP:DMG: - [1658142169.348227][2900:2905] CHIP:DMG: ], - [1658142169.348267][2900:2905] CHIP:DMG: - [1658142169.348300][2900:2905] CHIP:DMG: InteractionModelRevision = 1 - [1658142169.348331][2900:2905] CHIP:DMG: }, disabled: true - label: "Step 5d: TH reads the LockUserChange event from DUT" @@ -606,11 +596,9 @@ tests: disabled: true - label: - "Step 5e: TH sends Set Credential Command to DUT with the following - fields: 1.OperationType as 0-Add 2.Credential as 1 1- PIN, Index - 3.CredentialData as 123456 4.UserIndex as 1 5.UserStatus as null - 6.UserType as null" - PICS: DRLK.S.F08 && DRLK.S.C22.Rsp && DRLK.S.C23.Tx + "Step 5e: TH sends ClearCredential Command to DUT with the following + fields: a) CredentialType as 1, b) CredentialIndex as 2" + PICS: DRLK.S.F00 && DRLK.S.F08 && DRLK.S.C26.Tx verification: | ./chip-tool doorlock clear-credential '{ "credentialType" : 1 , "credentialIndex" : 2 }' 1 1 --timedInteractionTimeoutMs 1000 @@ -621,8 +609,15 @@ tests: [1658995364.178088][4383:4388] CHIP:DMG: { [1658995364.178137][4383:4388] CHIP:DMG: status = 0x00 (SUCCESS), [1658995364.178185][4383:4388] CHIP:DMG: }, + disabled: true - + - label: + "Step 5f: TH sends Set Credential Command to DUT with the following + fields: 1.OperationType as 0-Add 2.Credential as 1 1- PIN, Index + 3.CredentialData as 123456 4.UserIndex as 1 5.UserStatus as null + 6.UserType as null" + PICS: DRLK.S.F08 && DRLK.S.C22.Rsp && DRLK.S.C23.Tx + verification: | ./chip-tool doorlock set-user 0 1 xxx 6452 1 0 0 1 1 --timedInteractionTimeoutMs 1000 Via the TH (chip-tool), verify the SUCCESS response for setting the users details. @@ -631,17 +626,12 @@ tests: [1659778601.601636][3414:3419] CHIP:DMG: { [1659778601.601671][3414:3419] CHIP:DMG: status = 0x00 (SUCCESS), [1659778601.601713][3414:3419] CHIP:DMG: }, - [1659778601.601746][3414:3419] CHIP:DMG: - [1659778601.601785][3414:3419] CHIP:DMG: }, - [1659778601.601823][3414:3419] CHIP:DMG: + ./chip-tool doorlock set-credential 0 '{ "credentialType" : 1 , "credentialIndex" : 1 }' 123456 1 null null 1 1 --timedInteractionTimeoutMs 1000 Via the TH (chip-tool), verify the SUCCESS response for setting the credential details. - - [1658142472.351596][2966:2971] CHIP:DMG: }, - [1658142472.351698][2966:2971] CHIP:DMG: Received Command Response Data, Endpoint=1 Cluster=0x0000_0101 Command=0x0000_0023 [1658142472.351773][2966:2971] CHIP:TOO: Endpoint: 1 Cluster: 0x0000_0101 Command 0x0000_0023 [1658142472.351853][2966:2971] CHIP:TOO: SetCredentialResponse: { [1658142472.351910][2966:2971] CHIP:TOO: status: 0 @@ -650,7 +640,7 @@ tests: [1658142472.352012][2966:2971] CHIP:TOO: } disabled: true - - label: "Step 5f: TH reads the LockUserChange event from DUT" + - label: "Step 5g: TH reads the LockUserChange event from DUT" PICS: DRLK.S.E04 verification: | ./chip-tool doorlock read-event lock-user-change 1 1 @@ -690,34 +680,9 @@ tests: disabled: true - label: - "Step 5g: TH sends Clear User Command to DUT for user created in Step - 5a" - PICS: DRLK.S.C1d.Rsp - verification: | - ./chip-tool doorlock clear-user 2 1 1 --timedInteractionTimeoutMs 1000 - - Via the TH (chip-tool), verify the SUCCESS response for clearing the users details. - - [1658142762.492854][2993:2998] CHIP:DMG: - [1658142762.492888][2993:2998] CHIP:DMG: StatusIB = - [1658142762.492920][2993:2998] CHIP:DMG: { - [1658142762.492957][2993:2998] CHIP:DMG: status = 0x00 (SUCCESS), - [1658142762.492994][2993:2998] CHIP:DMG: }, - [1658142762.493026][2993:2998] CHIP:DMG: - [1658142762.493060][2993:2998] CHIP:DMG: }, - [1658142762.493097][2993:2998] CHIP:DMG: - [1658142762.493125][2993:2998] CHIP:DMG: }, - [1658142762.493158][2993:2998] CHIP:DMG: - [1658142762.493182][2993:2998] CHIP:DMG: ], - [1658142762.493211][2993:2998] CHIP:DMG: - [1658142762.493235][2993:2998] CHIP:DMG: InteractionModelRevision = 1 - [1658142762.493258][2993:2998] CHIP:DMG: }, - disabled: true - - - label: - "Step 6a: TH sends Clear Credential Command to DUT for Credential - created in Preconditions" - PICS: DRLK.S.C26.Rsp + "Step 6a: TH sends ClearCredential Command to DUT with the following + fields: a) CredentialType as 1, b) CredentialIndex as 1" + PICS: DRLK.S.F00 && DRLK.S.F08 && DRLK.S.C26.Rsp verification: | ./chip-tool doorlock clear-credential '{ "credentialType" : 1 , "credentialIndex" : 1 }' 1 1 --timedInteractionTimeoutMs 1000 @@ -727,40 +692,16 @@ tests: [1658142697.890058][2985:2990] CHIP:DMG: { [1658142697.890103][2985:2990] CHIP:DMG: status = 0x00 (SUCCESS), [1658142697.890147][2985:2990] CHIP:DMG: }, - [1658142697.890190][2985:2990] CHIP:DMG: - [1658142697.890229][2985:2990] CHIP:DMG: }, - [1658142697.890275][2985:2990] CHIP:DMG: - [1658142697.890312][2985:2990] CHIP:DMG: }, - [1658142697.890353][2985:2990] CHIP:DMG: - [1658142697.890385][2985:2990] CHIP:DMG: ], - [1658142697.890423][2985:2990] CHIP:DMG: - [1658142697.890455][2985:2990] CHIP:DMG: InteractionModelRevision = 1 - [1658142697.890486][2985:2990] CHIP:DMG: }, - [1658142697.890563][2985:2990] CHIP:DMG: Received Command Response Status for Endpoint=1 Cluster=0x0000_0101 Command=0x0000_0026 Status=0x0 - [1658142697.890616][2985:2990] CHIP:DMG: ICR moving to [AwaitingDe] disabled: true - label: "Step 6b: TH sends Clear User Command to DUT for user created in Preconditions" - PICS: DRLK.S.C1d.Rsp + PICS: DRLK.S.F08 && DRLK.S.C1d.Rsp verification: | - ./chip-tool doorlock clear-user 1 1 1 --timedInteractionTimeoutMs 1000 + ./chip-tool doorlock clear-user 0xFFFE 1 1 --timedInteractionTimeoutMs 1000 Via the TH (chip-tool), verify the SUCCESS response for clearing the users details. - [1658142762.492854][2993:2998] CHIP:DMG: - [1658142762.492888][2993:2998] CHIP:DMG: StatusIB = - [1658142762.492920][2993:2998] CHIP:DMG: { - [1658142762.492957][2993:2998] CHIP:DMG: status = 0x00 (SUCCESS), - [1658142762.492994][2993:2998] CHIP:DMG: }, - [1658142762.493026][2993:2998] CHIP:DMG: - [1658142762.493060][2993:2998] CHIP:DMG: }, - [1658142762.493097][2993:2998] CHIP:DMG: - [1658142762.493125][2993:2998] CHIP:DMG: }, - [1658142762.493158][2993:2998] CHIP:DMG: - [1658142762.493182][2993:2998] CHIP:DMG: ], - [1658142762.493211][2993:2998] CHIP:DMG: - [1658142762.493235][2993:2998] CHIP:DMG: InteractionModelRevision = 1 - [1658142762.493258][2993:2998] CHIP:DMG: }, + [1657631472.017012][2661:2666] CHIP:DMG: status = 0x00 (SUCCESS), disabled: true diff --git a/src/app/tests/suites/certification/Test_TC_DRLK_2_11.yaml b/src/app/tests/suites/certification/Test_TC_DRLK_2_11.yaml index de3a170c4f29fb..d159e70858ceb5 100644 --- a/src/app/tests/suites/certification/Test_TC_DRLK_2_11.yaml +++ b/src/app/tests/suites/certification/Test_TC_DRLK_2_11.yaml @@ -26,7 +26,7 @@ config: nodeId: 0x12344321 cluster: "Door Lock" endpoint: 1 - PINCredentialData: + CredentialData: type: octet_string defaultValue: "123456" RFIDCredentialData: @@ -45,55 +45,6 @@ tests: - name: "nodeId" value: nodeId - - label: "Precondition: Create new user with default parameters" - command: "SetUser" - timedInteractionTimeoutMs: 10000 - arguments: - values: - - name: "OperationType" - value: 0 - - name: "UserIndex" - value: 1 - - name: "UserName" - value: "xxx" - - name: "UserUniqueID" - value: 6452 - - name: "UserStatus" - value: 1 - - name: "UserType" - value: 0 - - name: "CredentialRule" - value: 0 - - - label: "Precondition: Read the user back and verify its fields" - command: "GetUser" - arguments: - values: - - name: "UserIndex" - value: 1 - response: - values: - - name: "UserIndex" - value: 1 - - name: "UserName" - value: "xxx" - - name: "UserUniqueID" - value: 6452 - - name: "UserStatus" - value: 1 - - name: "UserType" - value: 0 - - name: "CredentialRule" - value: 0 - - name: "Credentials" - value: [] - - name: "CreatorFabricIndex" - value: 1 - - name: "LastModifiedFabricIndex" - value: 1 - - name: "NextUserIndex" - value: null - - label: "Step 1a: TH reads NumberOfTotalUsersSupported and saves for future use." @@ -141,7 +92,28 @@ tests: minValue: 0 maxValue: 255 - - label: "Step 2a: TH sends Set Credential Command to DUT with type PIN" + - label: "Step 2a: TH sends SetUser Command to DUT" + PICS: DRLK.S.F08 && DRLK.S.C1a.Rsp + command: "SetUser" + timedInteractionTimeoutMs: 10000 + arguments: + values: + - name: "OperationType" + value: 0 + - name: "UserIndex" + value: 1 + - name: "UserName" + value: "xxx" + - name: "UserUniqueID" + value: 6452 + - name: "UserStatus" + value: 1 + - name: "UserType" + value: 0 + - name: "CredentialRule" + value: 0 + + - label: "Step 2b: TH sends Set Credential Command to DUT with type PIN" PICS: DRLK.S.F00 && DRLK.S.F08 && DRLK.S.C22.Rsp && DRLK.S.C23.Tx command: "SetCredential" timedInteractionTimeoutMs: 10000 @@ -152,7 +124,7 @@ tests: - name: "Credential" value: { CredentialType: 1, CredentialIndex: 1 } - name: "CredentialData" - value: PINCredentialData + value: CredentialData - name: "UserIndex" value: 1 - name: "UserStatus" diff --git a/src/app/tests/suites/certification/Test_TC_DRLK_2_6.yaml b/src/app/tests/suites/certification/Test_TC_DRLK_2_6.yaml index ba265fa99075de..7d3466aec33c91 100644 --- a/src/app/tests/suites/certification/Test_TC_DRLK_2_6.yaml +++ b/src/app/tests/suites/certification/Test_TC_DRLK_2_6.yaml @@ -107,40 +107,38 @@ tests: error: INVALID_COMMAND - label: - "Step 5: TH sends Get Holiday Schedule Command to DUT with Invalid - HolidayIndex as 15" + "Step 5: TH sends GetHolidaySchedule Command to DUT with Invalid + HolidayIndex > number_holiday_sch_supported ." PICS: DRLK.S.F0b && DRLK.S.C12.Rsp && DRLK.S.C12.Tx command: "GetHolidaySchedule" arguments: values: - name: "HolidayIndex" - value: 15 + value: NumberOfHolidaySchedulesSupportedValue + 1 response: values: - - name: "HolidayIndex" - value: 15 - name: "Status" - value: 133 + value: 0x85 - label: - "Step 6: TH sends Get Holiday Schedule Command to DUT with the - HolidayIndex as 10 (value is in the the range of step 1 but Holiday - Schedule entry not available)" + "Step 6: TH sends GetHolidaySchedule Command to DUT with the + HolidayIndex as any value that is different from 1 and value <= + number_holiday_sch_supported" PICS: DRLK.S.F0b && DRLK.S.C12.Rsp && DRLK.S.C12.Tx command: "GetHolidaySchedule" arguments: values: - name: "HolidayIndex" - value: 10 + value: NumberOfHolidaySchedulesSupportedValue response: values: - name: "HolidayIndex" - value: 10 + value: NumberOfHolidaySchedulesSupportedValue - name: "Status" - value: 139 + value: 0x8B - label: - "Step 7: TH send Clear Holiday Schedule Command to DUT with + "Step 7a: TH send Clear Holiday Schedule Command to DUT with HolidayIndex as 1" PICS: DRLK.S.F0b && DRLK.S.C13.Rsp command: "ClearHolidaySchedule" @@ -149,6 +147,18 @@ tests: - name: "HolidayIndex" value: 1 + - label: + "Step 7b: TH send Clear Holiday Schedule Command to DUT with + HolidayIndex as 0" + PICS: DRLK.S.F0b && DRLK.S.C13.Rsp + command: "ClearHolidaySchedule" + arguments: + values: + - name: "HolidayIndex" + value: 0 + response: + error: INVALID_COMMAND + - label: "Step 8: TH sends Get Holiday Schedule Command to DUT with HolidayIndex as 1" diff --git a/src/app/tests/suites/certification/Test_TC_DRLK_2_7.yaml b/src/app/tests/suites/certification/Test_TC_DRLK_2_7.yaml index 3cd01cdde0ecf8..892c4ae86ac1c3 100644 --- a/src/app/tests/suites/certification/Test_TC_DRLK_2_7.yaml +++ b/src/app/tests/suites/certification/Test_TC_DRLK_2_7.yaml @@ -35,55 +35,6 @@ tests: - name: "nodeId" value: nodeId - - label: "Precondition: Create new user" - command: "SetUser" - timedInteractionTimeoutMs: 10000 - arguments: - values: - - name: "OperationType" - value: 0 - - name: "UserIndex" - value: 1 - - name: "UserName" - value: "xxx" - - name: "UserUniqueID" - value: 6452 - - name: "UserStatus" - value: 1 - - name: "UserType" - value: 0 - - name: "CredentialRule" - value: 0 - - - label: "Precondition: Read the user back and verify its fields" - command: "GetUser" - arguments: - values: - - name: "UserIndex" - value: 1 - response: - values: - - name: "UserIndex" - value: 1 - - name: "UserName" - value: "xxx" - - name: "UserUniqueID" - value: 6452 - - name: "UserStatus" - value: 1 - - name: "UserType" - value: 0 - - name: "CredentialRule" - value: 0 - - name: "Credentials" - value: [] - - name: "CreatorFabricIndex" - value: 1 - - name: "LastModifiedFabricIndex" - value: 1 - - name: "NextUserIndex" - value: null - - label: "Step 1: TH reads NumberOfYearDay SchedulesSupportedPerUser attribute and saves for future use" @@ -109,9 +60,34 @@ tests: maxValue: 65534 - label: - "Step 3: TH sends Set Year Day Schedule Command to DUT with the - following values: a)YearDayIndex as 1 b)UserIndex as 1 - c)LocalStartTime as 960 Seconds d)LocalEndTime as 1980 Seconds" + "Step 3a: TH sends SetUser Command to DUT with the following values: + a)OperationType as 0, b)UserIndex as 1, c)UserName as xxx, + c)UserUniqueID as 6452, d)UserStatus as 1, e)UserType as 0, + f)CredentialRule as 0" + PICS: DRLK.S.F08 && DRLK.S.C1a.Rsp + command: "SetUser" + timedInteractionTimeoutMs: 10000 + arguments: + values: + - name: "OperationType" + value: 0 + - name: "UserIndex" + value: 1 + - name: "UserName" + value: "xxx" + - name: "UserUniqueID" + value: 6452 + - name: "UserStatus" + value: 1 + - name: "UserType" + value: 0 + - name: "CredentialRule" + value: 0 + + - label: + "Step 3b: TH sends SetYearDaySchedule Command to DUT with the + following values: a)YearDayIndex as 1, b)UserIndex as 1, + c)LocalStartTime as 960 Seconds, d)LocalEndTime as 1980 Seconds" PICS: DRLK.S.F0a && DRLK.S.C0e.Rsp command: "SetYearDaySchedule" arguments: @@ -196,9 +172,8 @@ tests: constraints: hasValue: false - - label: - "Step 7a: Create a new user with UserIndex as 5 then TH sends Get Year - Day Schedule Command to DUT with" + - label: "Step 7a: Create a new user with UserIndex as 5" + PICS: DRLK.S.F08 && DRLK.S.C1a.Rsp command: "SetUser" timedInteractionTimeoutMs: 10000 arguments: @@ -219,8 +194,9 @@ tests: value: 0 - label: - "Step 7b: YearDayIndex as 10 (value is in the the range of step 1 but - YearDay Schedule entry not available) : UserIndex as 5" + "Step 7b: TH sends Get Year Day Schedule Command to DUT with + YearDayIndex as 10 (value is in the the range of step 1 but YearDay + Schedule entry not available) : UserIndex as 5" PICS: DRLK.S.F0a && DRLK.S.C0f.Rsp && DRLK.S.C0f.Tx command: "GetYearDaySchedule" arguments: @@ -367,28 +343,11 @@ tests: response: error: INVALID_COMMAND - - label: "Clear a year day schedule for the first user" - PICS: DRLK.S.F0a && DRLK.S.C10.Rsp - command: "ClearYearDaySchedule" - arguments: - values: - - name: "YearDayIndex" - value: 1 - - name: "UserIndex" - value: 1 - - - label: "Cleanup the created user with UserIndex 1" - command: "ClearUser" + - label: "Step 14:TH sends ClearUser Command to DUT with the UserIndex as 1" + PICS: DRLK.S.F08 && DRLK.S.C1d.Rsp timedInteractionTimeoutMs: 10000 - arguments: - values: - - name: "UserIndex" - value: 1 - - - label: "Cleanup the created user with UserIndex 5" command: "ClearUser" - timedInteractionTimeoutMs: 10000 arguments: values: - name: "UserIndex" - value: 5 + value: 0xFFFE diff --git a/src/app/tests/suites/certification/Test_TC_DRLK_2_8.yaml b/src/app/tests/suites/certification/Test_TC_DRLK_2_8.yaml index 0204f30727a028..baece9a4d0aa95 100644 --- a/src/app/tests/suites/certification/Test_TC_DRLK_2_8.yaml +++ b/src/app/tests/suites/certification/Test_TC_DRLK_2_8.yaml @@ -206,6 +206,16 @@ tests: - name: "NextUserIndex" value: null + - label: "Step 6c: TH sends Get User Command to DUT with UserIndex as 0" + PICS: DRLK.S.C1a.Rsp && DRLK.S.C1b.Rsp && DRLK.S.C1c.Tx + command: "GetUser" + arguments: + values: + - name: "UserIndex" + value: 0 + response: + error: INVALID_COMMAND + - label: "Step 7: TH sends Set User Command to DUT with the following values: OperationType as 2 UserIndex as 2 UserName as NULL UserUniqueID as diff --git a/src/app/tests/suites/certification/Test_TC_IDM_3_2.yaml b/src/app/tests/suites/certification/Test_TC_IDM_3_2.yaml index 09982f6851b245..d9b37498847ce7 100644 --- a/src/app/tests/suites/certification/Test_TC_IDM_3_2.yaml +++ b/src/app/tests/suites/certification/Test_TC_IDM_3_2.yaml @@ -24,6 +24,13 @@ config: endpoint: 0 tests: + - label: "Note" + verification: | + 1. The Cluster and Commands should be based on the cluster implementation on the DUT. + 2. The cluster used in the below command is an example, User can use any supported chip cluster/attribute/command. + 3. Test Steps 2, 13, 20, 21, 22 and 23 cannot be executed with V1.0 SDK + disabled: true + - label: "Step 1: TH sends the WriteRequestMessage to the DUT to write one attribute on a given cluster and endpoint. On receipt of this message, @@ -93,7 +100,6 @@ tests: ./chip-tool basicinformation write local-config-disabled 1 1 0 - On TH(chip-tool), verify that DUT sends a WriteResponseMessage with the status set to Success for the data sent in the above command and verify by sending a ReadRequestMessage to read the value that was modified. [1686227392.013866][93552:93554] CHIP:DMG: WriteClient moving to [ResponseRe] [1686227392.013876][93552:93554] CHIP:DMG: WriteResponseMessage = @@ -144,7 +150,6 @@ tests: verification: | The cluster used in the below command is an example, User can use any supported chip cluster/attribute/command. - ./chip-tool basicinformation write node-label new 1 0 On TH(chip-tool), verify that DUT sends a WriteResponseMessage with the status set to Success for the data sent in the above command and veriffy by sending a ReadRequestMessage to read the value that was modified. @@ -195,7 +200,6 @@ tests: verification: | The cluster used in the below command is an example, User can use any supported chip cluster/attribute/command. - ./chip-tool any write-by-id 0x0008 0x0010 1 1 1 On TH(chip-tool), verify that DUT sends a WriteResponseMessage with the status set to Success for the data sent in the above command and verify by sending a ReadRequestMessage to read the value that was modified @@ -252,7 +256,7 @@ tests: verification: | DUT implementation required to verify write an attribute of data type signed integer. - If the Vendor DUT doesn't implement/supported this attribute, Please mark the test step as "\Not Applicable\" + If the Vendor DUT doesn't implement/supported this attribute, Please mark the test step as "Not Applicable" disabled: true - label: @@ -262,7 +266,7 @@ tests: verification: | DUT implementation required to verify write an attribute of data type float - If the Vendor DUT doesn't implement/supported this attribute, Please mark the test step as "\Not Applicable\" + If the Vendor DUT doesn't implement/supported this attribute, Please mark the test step as "Not Applicable" disabled: true - label: @@ -272,7 +276,7 @@ tests: verification: | DUT implementation required to verify write an attribute of data type Octet String - If the Vendor DUT doesn't implement/supported this attribute, Please mark the test step as "\Not Applicable\" + If the Vendor DUT doesn't implement/supported this attribute, Please mark the test step as "Not Applicable" disabled: true - label: @@ -282,7 +286,7 @@ tests: verification: | DUT implementation required to verify write an attribute ofdata type Struct - If the Vendor DUT doesn't implement/supported this attribute, Please mark the test step as "\Not Applicable\" + If the Vendor DUT doesn't implement/supported this attribute, Please mark the test step as "Not Applicable" disabled: true - label: @@ -292,7 +296,7 @@ tests: verification: | DUT implementation required to verify write an attribute of data type List - If the Vendor DUT doesn't implement/supported this attribute, Please mark the test step as "\Not Applicable\" + If the Vendor DUT doesn't implement/supported this attribute, Please mark the test step as "Not Applicable" disabled: true - label: @@ -301,6 +305,7 @@ tests: PICS: MCORE.IDM.S.Attribute_W.DataType_Enum verification: | The cluster used in the below command is an example, User can use any supported chip cluster/attribute/command. + ./chip-tool any write-by-id 0x0204 0 1 1 1 On TH(chip-tool), verify that DUT sends a WriteResponseMessage with the status set to Success for the data sent in the above command and verify by sending a ReadRequestMessage to read the value that was modified. @@ -339,9 +344,7 @@ tests: ./chip-tool any read-by-id 0x0204 0 1 1 On TH(chip-tool), verify the attribute value that was modified in above step - [1686227658.643633][93610:93612] CHIP:DMG: SuppressResponse = true, - [1686227658.643643][93610:93612] CHIP:DMG: InteractionModelRevision = 1 - [1686227658.643651][93610:93612] CHIP:DMG: } + [1686227658.643729][93610:93612] CHIP:TOO: Endpoint: 1 Cluster: 0x0000_0204 Attribute 0x0000_0000 DataVersion: 1939013916 [1686227658.643765][93610:93612] CHIP:TOO: TemperatureDisplayMode: 1 [1686227658.643826][93610:93612] CHIP:EM: <<< [E:7325i S:64158 M:55416058 (Ack:52160854)] (S) Msg TX to 1:0000000000000001 [58B9] --- Type 0000:10 (SecureChannel:StandaloneAck) @@ -392,14 +395,10 @@ tests: [1686227690.827552][93615:93617] CHIP:EM: Flushed pending ack for MessageCounter:75613172 on exchange 7166i - ./chip-tool colorcontrol read-by-id 0x000f 1 1 On TH(chip-tool), verify the attribute value that was modified in above step - [1686227710.429709][93622:93624] CHIP:DMG: SuppressResponse = true, - [1686227710.429715][93622:93624] CHIP:DMG: InteractionModelRevision = 1 - [1686227710.429720][93622:93624] CHIP:DMG: } [1686227710.429791][93622:93624] CHIP:TOO: Endpoint: 1 Cluster: 0x0000_0300 Attribute 0x0000_000F DataVersion: 861636757 [1686227710.429832][93622:93624] CHIP:TOO: Options: 1 [1686227710.429882][93622:93624] CHIP:EM: <<< [E:4989i S:43440 M:30144210 (Ack:113240090)] (S) Msg TX to 1:0000000000000001 [58B9] --- Type 0000:10 (SecureChannel:StandaloneAck) @@ -468,7 +467,6 @@ tests: On TH(chip-tool), Verify that the DUT sends the status code UNSUPPORTED_CLUSTER for the data sent in the above command - [1686229393.093998][94065:94067] CHIP:EM: Rxd Ack; Removing MessageCounter:56487501 from Retrans Table on exchange 60736i [1686229393.094012][94065:94067] CHIP:DMG: WriteClient moving to [ResponseRe] [1686229393.094033][94065:94067] CHIP:DMG: WriteResponseMessage = @@ -591,11 +589,11 @@ tests: verification: | The cluster used in the below command is an example, User can use any supported chip cluster/attribute/command. - To Setup the TH such that it should not have the privilege for the cluster in the path. , 1st we need to send below mentioned ACL command Here by sending below mentioned ACL command giving only access for ACL cluster(31), So except identify cluster command if try to send any other command will get status as unsupported access. ./chip-tool accesscontrol write acl '[{"fabricIndex": 1, "privilege": 5, "authMode": 2, "subjects":[112233], "targets": [{ "cluster":31, "endpoint":0, "deviceType":null }]}]' 1 0 + On TH(chip-tool), Verify that the DUT sends the status code SUCCESS for the data sent in the above command [1686228784.940429][93932:93934] CHIP:EM: Rxd Ack; Removing MessageCounter:76693936 from Retrans Table on exchange 64135i @@ -696,6 +694,7 @@ tests: The cluster used in the below command is an example, User can use any supported chip cluster/attribute/command. ./chip-tool levelcontrol write on-level 2 1 1 + On TH(chip-tool), verify that DUT sends a Write Response message with a success [1686229097.486513][93998:94000] CHIP:DMG: WriteClient moving to [ResponseRe] [1686229097.486533][93998:94000] CHIP:DMG: WriteResponseMessage = @@ -731,8 +730,9 @@ tests: ./chip-tool levelcontrol read on-level 1 1 + On TH(chip-tool), verify the attribute value that was modified in above step. - [1686229128.655083][94004:94006] CHIP:DMG: } + [1686229128.655203][94004:94006] CHIP:TOO: Endpoint: 1 Cluster: 0x0000_0008 Attribute 0x0000_0011 DataVersion: 2737039619 [1686229128.655240][94004:94006] CHIP:TOO: OnLevel: 2 [1686229128.655340][94004:94006] CHIP:EM: <<< [E:43327i S:11132 M:58615041 (Ack:232232518)] (S) Msg TX to 1:0000000000000001 [58B9] --- Type 0000:10 (SecureChannel:StandaloneAck) @@ -741,7 +741,9 @@ tests: ./chip-tool levelcontrol write on-level 3 1 1 + Verify on TH(chip-tool) receives WriteResponseMessage with the status set to Success for the data sent in the above command and verify by sending a ReadRequestMessage to read the value that was modified. + [1686229150.157900][94008:94010] CHIP:DMG: WriteClient moving to [ResponseRe] [1686229150.157919][94008:94010] CHIP:DMG: WriteResponseMessage = [1686229150.157927][94008:94010] CHIP:DMG: { @@ -776,6 +778,7 @@ tests: ./chip-tool levelcontrol read on-level 1 1 On TH(chip-tool), verify the attribute value that was modified in above step + [1686229175.161437][94014:94016] CHIP:DMG: InteractionModelRevision = 1 [1686229175.161442][94014:94016] CHIP:DMG: } [1686229175.161512][94014:94016] CHIP:TOO: Endpoint: 1 Cluster: 0x0000_0008 Attribute 0x0000_0011 DataVersion: 2737039620 @@ -788,6 +791,7 @@ tests: ./chip-tool levelcontrol write on-level 1 1 1 Verify on TH(chip-tool) receives WriteResponseMessage with the status set to Success for the data sent in the above command and verify by sending a ReadRequestMessage to read the value that was modified + [1686229199.082595][94020:94022] CHIP:EM: Rxd Ack; Removing MessageCounter:90418515 from Retrans Table on exchange 55893i [1686229199.082606][94020:94022] CHIP:DMG: WriteClient moving to [ResponseRe] [1686229199.082625][94020:94022] CHIP:DMG: WriteResponseMessage = @@ -823,6 +827,7 @@ tests: ./chip-tool levelcontrol read on-level 1 1 On TH(chip-tool), verify the attribute value that was modified in above step + [1686229224.244504][94026:94028] CHIP:DMG: } [1686229224.244574][94026:94028] CHIP:TOO: Endpoint: 1 Cluster: 0x0000_0008 Attribute 0x0000_0011 DataVersion: 2737039621 [1686229224.244614][94026:94028] CHIP:TOO: OnLevel: 1 @@ -893,6 +898,7 @@ tests: ./chip-tool levelcontrol write on-level 3 1 1 --data-version 0xc4c9d7ae On TH(chip-tool), verify that DUT sends a Write Response message with a success + [1686229590.858793][94124:94126] CHIP:DMG: WriteClient moving to [ResponseRe] [1686229590.858813][94124:94126] CHIP:DMG: WriteResponseMessage = [1686229590.858821][94124:94126] CHIP:DMG: { @@ -926,6 +932,7 @@ tests: ./chip-tool levelcontrol read on-level 1 1 On TH(chip-tool), verify that TH receives the WriteResponseMessage with the status set to Success for the data sent in the above command and veriy by sending a ReadRequestMessage to read the value that was modified. + [1686229613.307472][94130:94132] CHIP:DMG: } [1686229613.307596][94130:94132] CHIP:TOO: Endpoint: 1 Cluster: 0x0000_0008 Attribute 0x0000_0011 DataVersion: 2737039622 [1686229613.307632][94130:94132] CHIP:TOO: OnLevel: 3 @@ -945,8 +952,6 @@ tests: verification: | The cluster used in the below command is an example, User can use any supported chip cluster/attribute/command. - - ./chip-tool levelcontrol read on-level 1 1 On TH(chip-tool), Verify that DUT is responds with attribute value @@ -962,7 +967,9 @@ tests: ./chip-tool levelcontrol write on-level 4 1 1 + On TH(chip-tool), DUT send a Write Response message with status code as success + [1686229675.214378][94147:94149] CHIP:DMG: WriteResponseMessage = [1686229675.214386][94147:94149] CHIP:DMG: { [1686229675.214393][94147:94149] CHIP:DMG: AttributeStatusIBs = diff --git a/src/app/tests/suites/certification/Test_TC_IDM_5_2.yaml b/src/app/tests/suites/certification/Test_TC_IDM_5_2.yaml index 534154cb6d7e3c..3a7e8f9360df56 100644 --- a/src/app/tests/suites/certification/Test_TC_IDM_5_2.yaml +++ b/src/app/tests/suites/certification/Test_TC_IDM_5_2.yaml @@ -80,7 +80,9 @@ tests: By sending a ReadRequest that the Write action was performed correctly. + ./chip-tool levelcontrol read on-level 1 1 + [1655799013.466144][7075:7080] CHIP:TOO: Endpoint: 1 Cluster: 0x0000_0008 Attribute 0x0000_0011 DataVersion: 737039642 [1655799013.466237][7075:7080] CHIP:TOO: on level: 1 [1655799013.466336][7075:7080] CHIP:EM: Sending Standalone Ack for MessageCounter:202402347 on exchange 58038i @@ -92,21 +94,27 @@ tests: status response message to be received. Wait for 5 seconds(Timer has expired) and then send the Invoke Request Message to the DUT." verification: | - ./chip-tool onoff on 1 1 --repeat-delay-ms 5000 --timedInteractionTimeoutMs 200 - - If the device being certified is Matter release 1.4 or later, verify DUT sends back a Status response with the TIMEOUT status code. - If the device being certified is Matter release 1.3 or earlier, verify the DUT sends back a Status response with either TIMEOUT or UNSUPPORTED_ACCESS status code. - - [1718641649.158222][1587554:1587556] CHIP:EM: Rxd Ack; Removing MessageCounter:70404585 from Retrans Table on exchange 44026i - [1718641649.158241][1587554:1587556] CHIP:DMG: ICR moving to [ResponseRe] - [1718641649.158261][1587554:1587556] CHIP:DMG: StatusResponseMessage = - [1718641649.158276][1587554:1587556] CHIP:DMG: { - [1718641649.158290][1587554:1587556] CHIP:DMG: Status = 0x94 (TIMEOUT), - [1718641649.158304][1587554:1587556] CHIP:DMG: InteractionModelRevision = 11 - [1718641649.158318][1587554:1587556] CHIP:DMG: } - [1718641649.158332][1587554:1587556] CHIP:IM: Received status response, status is 0x94 (TIMEOUT) - [1718641649.158355][1587554:1587556] CHIP:TOO: Error: IM Error 0x00000594: General error: 0x94 (TIMEOUT) - + On TH(chip-tool), Verify the DUT's response based on Matter release versions: + + For Matter release 1.4 or later, verify that the DUT responds with a status of TIMEOUT (0x94). + For Matter release 1.3 or earlier, verify that the DUT sends back a Status response with either TIMEOUT (0x94) or UNSUPPORTED_ACCESS (0x7e). + + ./chip-tool onoff on 1 1 --repeat-delay-ms 1000 --timedInteractionTimeoutMs 500 + + [1720440076.857] [11806:11808] [EM] Found matching exchange: 17395i, Delegate: 0xffff7c008198 + [1720440076.857] [11806:11808] [EM] CHIP MessageCounter:107413542 not in RetransTable on exchange 17395i + [1720440076.858] [11806:11808] [EM] >>> [E:17395i S:706 M:204574784 (Ack:107413543)] (S) Msg RX from 1:0000000000000001 [67EF] --- Type 0001:01 (IM:StatusResponse) + [1720440076.858] [11806:11808] [EM] Found matching exchange: 17395i, Delegate: 0xffff7c008198 + [1720440076.858] [11806:11808] [EM] Rxd Ack; Removing MessageCounter:107413543 from Retrans Table on exchange 17395i + [1720440076.858] [11806:11808] [DMG] ICR moving to [ResponseRe] + [1720440076.858] [11806:11808] [DMG] StatusResponseMessage = + [1720440076.858] [11806:11808] [DMG] { + [1720440076.858] [11806:11808] [DMG] Status = 0x94 (TIMEOUT), + [1720440076.858] [11806:11808] [DMG] InteractionModelRevision = 11 + [1720440076.858] [11806:11808] [DMG] } + [1720440076.858] [11806:11808] [IM] Received status response, status is 0x94 (TIMEOUT) + [1720440076.858] [11806:11808] [TOO] Error: IM Error 0x00000594: General error: 0x94 (TIMEOUT) + [1720440076.858] [11806:11808] [EM] <<< [E:17395i S:706 M:107413546 (Ack:204574784)] (S) Msg TX to 1:0000000000000001 [67EF] [UDP:[fe80::e65f:1ff:fe49:ae1a%eth0]:5540] --- Type 0001:01 (IM:StatusResponse) disabled: true - label: @@ -115,18 +123,26 @@ tests: status response message to be received. Wait for 5 seconds(Timer has expired) and then send the Write Request Message to the DUT." verification: | - ./chip-tool modeselect write on-mode 0 1 1 --repeat-delay-ms 1000 --timedInteractionTimeoutMs 500 + On TH(chip-tool), Verify the DUT's response based on Matter release versions: - If the device being certified is Matter release 1.4 or later, verify DUT sends back a Status response with the TIMEOUT status code. - If the device being certified is Matter release 1.3 or earlier, verify the DUT sends back a Status response with either TIMEOUT or UNSUPPORTED_ACCESS status code. + For Matter release 1.4 or later, verify that the DUT responds with a status of TIMEOUT (0x94). + For Matter release 1.3 or earlier, verify that the DUT sends back a Status response with either TIMEOUT (0x94) or UNSUPPORTED_ACCESS (0x7e). - [1720104134.620521][3325282:3325284] CHIP:DMG: WriteClient moving to [ResponseRe] - [1720104134.620540][3325282:3325284] CHIP:DMG: StatusResponseMessage = - [1720104134.620555][3325282:3325284] CHIP:DMG: { - [1720104134.620569][3325282:3325284] CHIP:DMG: Status = 0x94 (TIMEOUT), - [1720104134.620583][3325282:3325284] CHIP:DMG: InteractionModelRevision = 11 - [1720104134.620596][3325282:3325284] CHIP:DMG: } - [1720104134.620611][3325282:3325284] CHIP:IM: Received status response, status is 0x94 (TIMEOUT) - [1720104134.620689][3325282:3325284] CHIP:TOO: Error: IM Error 0x00000594: General error: 0x94 (TIMEOUT) + ./chip-tool modeselect write on-mode 0 1 1 --repeat-delay-ms 1000 --timedInteractionTimeoutMs 500 + On TH(chip-tool), Verify we are getting status response and UNSUPPORTED_ACCESS from DUT to TH for above command + + + [1720440084.666] [11810:11812] [EM] Found matching exchange: 22607i, Delegate: 0xffff9000a040 + [1720440084.666] [11810:11812] [EM] Rxd Ack; Removing MessageCounter:200551619 from Retrans Table on exchange 22607i + [1720440084.667] [11810:11812] [DMG] WriteClient moving to [ResponseRe] + [1720440084.667] [11810:11812] [DMG] StatusResponseMessage = + [1720440084.667] [11810:11812] [DMG] { + [1720440084.667] [11810:11812] [DMG] Status = 0x94 (TIMEOUT), + [1720440084.667] [11810:11812] [DMG] InteractionModelRevision = 11 + [1720440084.667] [11810:11812] [DMG] } + [1720440084.667] [11810:11812] [IM] Received status response, status is 0x94 (TIMEOUT) + [1720440084.667] [11810:11812] [TOO] Error: IM Error 0x00000594: General error: 0x94 (TIMEOUT) + [1720440084.667] [11810:11812] [EM] <<< [E:22607i S:23365 M:200551622 (Ack:3244934)] (S) Msg TX to 1:0000000000000001 [67EF] [UDP:[fe80::e65f:1ff:fe49:ae1a%eth0]:5540] --- Type 0001:01 (IM:StatusResponse) + [1720440084.668] [11810:11812] [DMG] WriteClient moving to [AwaitingDe] disabled: true diff --git a/src/app/tests/suites/certification/Test_TC_IDM_6_1.yaml b/src/app/tests/suites/certification/Test_TC_IDM_6_1.yaml index 31339d68e30856..b9ade1e9e42e05 100644 --- a/src/app/tests/suites/certification/Test_TC_IDM_6_1.yaml +++ b/src/app/tests/suites/certification/Test_TC_IDM_6_1.yaml @@ -523,7 +523,6 @@ tests: verification: | ./chip-tool any read-event-by-id 0xFFFFFFFF 0xFFFFFFFF 1 0xFFFF - On TH(chip-tool) verify that the DUT sends Report Data Message with EventReports [1655986008.400642][4813:4818] CHIP:DMG: } @@ -609,7 +608,7 @@ tests: large event data. For every chunked data message received, DUT sends a status response." verification: | - Try to verify this step in doorlock app, below mentioned wildcard command to read-event from TH to DUT. + Try to verify this step in doorlock app, below mentioned wildcard command to read-event from TH to DUT.(Log may vary based on the reference app) ./chip-tool any read-event-by-id 0xFFFFFFFF 0xFFFFFFFF 1 0xFFFF The message flow can expect as mentioned below diff --git a/src/app/tests/suites/certification/Test_TC_LVL_8_1.yaml b/src/app/tests/suites/certification/Test_TC_LVL_8_1.yaml new file mode 100644 index 00000000000000..20fad8de4c7dfc --- /dev/null +++ b/src/app/tests/suites/certification/Test_TC_LVL_8_1.yaml @@ -0,0 +1,361 @@ +# 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. +# Auto-generated scripts for harness use only, please review before automation. The endpoints and cluster names are currently set to default + +name: 32.1.9 [TC-LVL-8.1] Verification of commands (DUT as Client)" + +PICS: + - LVL.C + +config: + nodeId: 0x12344321 + cluster: "Basic Information" + endpoint: 0 + +tests: + - label: + "Step 1: TH prompts the operator to make the DUT send one or more + supported commands from the Level Control cluster" + verification: | + The product maker needs to provide instructions on how to trigger the command on the DUT. For comparison, the DUT behavior for this test step can be simulated using chip-tool (when the DUT is a commissioner/Client). + + The DUT should transmit correctly formed commands in any order, with application achievable values that are within the limits allowed by the specification and consistent with the attribute values reported by the TH. Verify this using the TH log of these messages. + + Please send the below command and validate the conditions mentioned above: + + ./chip-tool levelcontrol move-to-level 125 10 0 0 1 1 + on TH (all-clusters-app) log. Verify the move-to-level command is received successfully. + + + [1721131579.026] [3188:3188] [DL] HandlePlatformSpecificBLEEvent 32792 + [1721131579.028] [3188:3188] [EM] >>> [E:39794r S:12010 M:201578598] (S) Msg RX from 1:000000000001B669 [AD70] --- Type 0001:08 (IM:InvokeCommandRequest) + [1721131579.028] [3188:3188] [EM] Handling via exchange: 39794r, Delegate: 0xaaaac95ff3b0 + [1721131579.028] [3188:3188] [DMG] InvokeRequestMessage = + [1721131579.028] [3188:3188] [DMG] { + [1721131579.029] [3188:3188] [DMG] suppressResponse = false, + [1721131579.029] [3188:3188] [DMG] timedRequest = false, + [1721131579.029] [3188:3188] [DMG] InvokeRequests = + [1721131579.029] [3188:3188] [DMG] [ + [1721131579.029] [3188:3188] [DMG] CommandDataIB = + [1721131579.029] [3188:3188] [DMG] { + [1721131579.029] [3188:3188] [DMG] CommandPathIB = + [1721131579.029] [3188:3188] [DMG] { + [1721131579.029] [3188:3188] [DMG] EndpointId = 0x1, + [1721131579.029] [3188:3188] [DMG] ClusterId = 0x8, + [1721131579.029] [3188:3188] [DMG] CommandId = 0x0, + [1721131579.029] [3188:3188] [DMG] }, + [1721131579.029] [3188:3188] [DMG] + [1721131579.029] [3188:3188] [DMG] CommandFields = + [1721131579.029] [3188:3188] [DMG] { + [1721131579.029] [3188:3188] [DMG] 0x0 = 125 (unsigned), + [1721131579.029] [3188:3188] [DMG] 0x1 = 10 (unsigned), + [1721131579.029] [3188:3188] [DMG] 0x2 = 0 (unsigned), + [1721131579.029] [3188:3188] [DMG] 0x3 = 0 (unsigned), + [1721131579.029] [3188:3188] [DMG] }, + [1721131579.029] [3188:3188] [DMG] }, + [1721131579.029] [3188:3188] [DMG] + [1721131579.029] [3188:3188] [DMG] ], + [1721131579.029] [3188:3188] [DMG] + [1721131579.029] [3188:3188] [DMG] InteractionModelRevision = 11 + [1721131579.029] [3188:3188] [DMG] }, + [1721131579.029] [3188:3188] [DMG] AccessControl: checking f=1 a=c s=0x000000000001B669 t= c=0x0000_0008 e=1 p=o + [1721131579.029] [3188:3188] [DMG] AccessControl: allowed + [1721131579.030] [3188:3188] [DMG] Received command for Endpoint=1 Cluster=0x0000_0008 Command=0x0000_0000 + [1721131579.030] [3188:3188] [ZCL] RX level-control: MOVE_TO_LEVEL 7d a 0 0 + [1721131579.030] [3188:3188] [DMG] Command handler moving to [NewRespons] + + + + ./chip-tool levelcontrol move 1 5 0 0 1 1 + on TH (all-clusters-app) log. Verify the move command is received successfully. + + + [1721131607.050] [3188:3188] [EM] Handling via exchange: 48182r, Delegate: 0xaaaac95ff3b0 + [1721131607.050] [3188:3188] [DMG] InvokeRequestMessage = + [1721131607.050] [3188:3188] [DMG] { + [1721131607.050] [3188:3188] [DMG] suppressResponse = false, + [1721131607.050] [3188:3188] [DMG] timedRequest = false, + [1721131607.050] [3188:3188] [DMG] InvokeRequests = + [1721131607.050] [3188:3188] [DMG] [ + [1721131607.050] [3188:3188] [DMG] CommandDataIB = + [1721131607.050] [3188:3188] [DMG] { + [1721131607.050] [3188:3188] [DMG] CommandPathIB = + [1721131607.050] [3188:3188] [DMG] { + [1721131607.050] [3188:3188] [DMG] EndpointId = 0x1, + [1721131607.050] [3188:3188] [DMG] ClusterId = 0x8, + [1721131607.050] [3188:3188] [DMG] CommandId = 0x1, + [1721131607.050] [3188:3188] [DMG] }, + [1721131607.050] [3188:3188] [DMG] + [1721131607.050] [3188:3188] [DMG] CommandFields = + [1721131607.050] [3188:3188] [DMG] { + [1721131607.050] [3188:3188] [DMG] 0x0 = 1 (unsigned), + [1721131607.050] [3188:3188] [DMG] 0x1 = 5 (unsigned), + [1721131607.050] [3188:3188] [DMG] 0x2 = 0 (unsigned), + [1721131607.050] [3188:3188] [DMG] 0x3 = 0 (unsigned), + [1721131607.050] [3188:3188] [DMG] }, + [1721131607.050] [3188:3188] [DMG] }, + [1721131607.050] [3188:3188] [DMG] + [1721131607.050] [3188:3188] [DMG] ], + [1721131607.050] [3188:3188] [DMG] + [1721131607.050] [3188:3188] [DMG] InteractionModelRevision = 11 + [1721131607.050] [3188:3188] [DMG] }, + [1721131607.051] [3188:3188] [DMG] AccessControl: checking f=1 a=c s=0x000000000001B669 t= c=0x0000_0008 e=1 p=o + [1721131607.051] [3188:3188] [DMG] AccessControl: allowed + [1721131607.051] [3188:3188] [DMG] Received command for Endpoint=1 Cluster=0x0000_0008 Command=0x0000_0001 + + + + ./chip-tool levelcontrol step 1 10 0 0 0 1 1 + on TH (all-clusters-app) log. Verify the step command is received successfully. + + [1721131622.700] [3188:3188] [EM] Handling via exchange: 33066r, Delegate: 0xaaaac95ff3b0 + [1721131622.700] [3188:3188] [DMG] InvokeRequestMessage = + [1721131622.700] [3188:3188] [DMG] { + [1721131622.700] [3188:3188] [DMG] suppressResponse = false, + [1721131622.700] [3188:3188] [DMG] timedRequest = false, + [1721131622.700] [3188:3188] [DMG] InvokeRequests = + [1721131622.700] [3188:3188] [DMG] [ + [1721131622.700] [3188:3188] [DMG] CommandDataIB = + [1721131622.700] [3188:3188] [DMG] { + [1721131622.700] [3188:3188] [DMG] CommandPathIB = + [1721131622.700] [3188:3188] [DMG] { + [1721131622.700] [3188:3188] [DMG] EndpointId = 0x1, + [1721131622.700] [3188:3188] [DMG] ClusterId = 0x8, + [1721131622.700] [3188:3188] [DMG] CommandId = 0x2, + [1721131622.700] [3188:3188] [DMG] }, + [1721131622.700] [3188:3188] [DMG] + [1721131622.701] [3188:3188] [DMG] CommandFields = + [1721131622.701] [3188:3188] [DMG] { + [1721131622.701] [3188:3188] [DMG] 0x0 = 1 (unsigned), + [1721131622.701] [3188:3188] [DMG] 0x1 = 10 (unsigned), + [1721131622.701] [3188:3188] [DMG] 0x2 = 0 (unsigned), + [1721131622.701] [3188:3188] [DMG] 0x3 = 0 (unsigned), + [1721131622.701] [3188:3188] [DMG] 0x4 = 0 (unsigned), + [1721131622.701] [3188:3188] [DMG] }, + [1721131622.701] [3188:3188] [DMG] }, + [1721131622.701] [3188:3188] [DMG] + [1721131622.701] [3188:3188] [DMG] ], + [1721131622.701] [3188:3188] [DMG] + [1721131622.701] [3188:3188] [DMG] InteractionModelRevision = 11 + [1721131622.701] [3188:3188] [DMG] }, + [1721131622.701] [3188:3188] [DMG] AccessControl: checking f=1 a=c s=0x000000000001B669 t= c=0x0000_0008 e=1 p=o + [1721131622.701] [3188:3188] [DMG] AccessControl: allowed + + + + + ./chip-tool levelcontrol stop 0 0 1 1 + on TH (all-clusters-app) log. Verify the stop command is received successfully. + + [1721131637.910] [3188:3188] [EM] >>> [E:64966r S:12013 M:241263255] (S) Msg RX from 1:000000000001B669 [AD70] --- Type 0001:08 (IM:InvokeCommandRequest) + [1721131637.910] [3188:3188] [EM] Handling via exchange: 64966r, Delegate: 0xaaaac95ff3b0 + [1721131637.910] [3188:3188] [DMG] InvokeRequestMessage = + [1721131637.910] [3188:3188] [DMG] { + [1721131637.910] [3188:3188] [DMG] suppressResponse = false, + [1721131637.910] [3188:3188] [DMG] timedRequest = false, + [1721131637.910] [3188:3188] [DMG] InvokeRequests = + [1721131637.910] [3188:3188] [DMG] [ + [1721131637.910] [3188:3188] [DMG] CommandDataIB = + [1721131637.910] [3188:3188] [DMG] { + [1721131637.910] [3188:3188] [DMG] CommandPathIB = + [1721131637.910] [3188:3188] [DMG] { + [1721131637.910] [3188:3188] [DMG] EndpointId = 0x1, + [1721131637.910] [3188:3188] [DMG] ClusterId = 0x8, + [1721131637.910] [3188:3188] [DMG] CommandId = 0x3, + [1721131637.910] [3188:3188] [DMG] }, + [1721131637.910] [3188:3188] [DMG] + [1721131637.910] [3188:3188] [DMG] CommandFields = + [1721131637.910] [3188:3188] [DMG] { + [1721131637.910] [3188:3188] [DMG] 0x0 = 0 (unsigned), + [1721131637.910] [3188:3188] [DMG] 0x1 = 0 (unsigned), + [1721131637.910] [3188:3188] [DMG] }, + [1721131637.910] [3188:3188] [DMG] }, + [1721131637.910] [3188:3188] [DMG] + [1721131637.911] [3188:3188] [DMG] ], + [1721131637.911] [3188:3188] [DMG] + [1721131637.911] [3188:3188] [DMG] InteractionModelRevision = 11 + [1721131637.911] [3188:3188] [DMG] }, + [1721131637.911] [3188:3188] [DMG] AccessControl: checking f=1 a=c s=0x000000000001B669 t= c=0x0000_0008 e=1 p=o + [1721131637.911] [3188:3188] [DMG] AccessControl: allowed + [1721131637.911] [3188:3188] [DMG] Received command for Endpoint=1 Cluster=0x0000_0008 Command=0x0000_0003 + + + + ./chip-tool levelcontrol move-to-level-with-on-off 1 0 0 0 1 1 + on TH (all-clusters-app) log. Verify the move-to-level-with-on-off command is received successfully. + + [1721131651.510] [3188:3188] [EM] >>> [E:42083r S:12014 M:199867058] (S) Msg RX from 1:000000000001B669 [AD70] --- Type 0001:08 (IM:InvokeCommandRequest) + [1721131651.510] [3188:3188] [EM] Handling via exchange: 42083r, Delegate: 0xaaaac95ff3b0 + [1721131651.510] [3188:3188] [DMG] InvokeRequestMessage = + [1721131651.510] [3188:3188] [DMG] { + [1721131651.510] [3188:3188] [DMG] suppressResponse = false, + [1721131651.510] [3188:3188] [DMG] timedRequest = false, + [1721131651.510] [3188:3188] [DMG] InvokeRequests = + [1721131651.510] [3188:3188] [DMG] [ + [1721131651.510] [3188:3188] [DMG] CommandDataIB = + [1721131651.510] [3188:3188] [DMG] { + [1721131651.511] [3188:3188] [DMG] CommandPathIB = + [1721131651.511] [3188:3188] [DMG] { + [1721131651.511] [3188:3188] [DMG] EndpointId = 0x1, + [1721131651.511] [3188:3188] [DMG] ClusterId = 0x8, + [1721131651.511] [3188:3188] [DMG] CommandId = 0x4, + [1721131651.511] [3188:3188] [DMG] }, + [1721131651.511] [3188:3188] [DMG] + [1721131651.511] [3188:3188] [DMG] CommandFields = + [1721131651.511] [3188:3188] [DMG] { + [1721131651.511] [3188:3188] [DMG] 0x0 = 1 (unsigned), + [1721131651.511] [3188:3188] [DMG] 0x1 = 0 (unsigned), + [1721131651.511] [3188:3188] [DMG] 0x2 = 0 (unsigned), + [1721131651.511] [3188:3188] [DMG] 0x3 = 0 (unsigned), + [1721131651.511] [3188:3188] [DMG] }, + [1721131651.511] [3188:3188] [DMG] }, + [1721131651.511] [3188:3188] [DMG] + [1721131651.511] [3188:3188] [DMG] ], + [1721131651.511] [3188:3188] [DMG] + [1721131651.511] [3188:3188] [DMG] InteractionModelRevision = 11 + [1721131651.511] [3188:3188] [DMG] }, + [1721131651.512] [3188:3188] [DMG] AccessControl: checking f=1 a=c s=0x000000000001B669 t= c=0x0000_0008 e=1 p=o + [1721131651.512] [3188:3188] [DMG] AccessControl: allowed + [1721131651.512] [3188:3188] [DMG] Received command for Endpoint=1 Cluster=0x0000_0008 Command=0x0000_0004 + + + + + ./chip-tool levelcontrol move-with-on-off 1 5 0 0 1 1 + on TH (all-clusters-app) log. Verify the move-with-on-off command is received successfully. + + + [1721131674.036] [3188:3188] [DL] HandlePlatformSpecificBLEEvent 32792 + [1721131674.039] [3188:3188] [EM] >>> [E:21646r S:12015 M:111324533] (S) Msg RX from 1:000000000001B669 [AD70] --- Type 0001:08 (IM:InvokeCommandRequest) + [1721131674.039] [3188:3188] [EM] Handling via exchange: 21646r, Delegate: 0xaaaac95ff3b0 + [1721131674.039] [3188:3188] [DMG] InvokeRequestMessage = + [1721131674.039] [3188:3188] [DMG] { + [1721131674.039] [3188:3188] [DMG] suppressResponse = false, + [1721131674.039] [3188:3188] [DMG] timedRequest = false, + [1721131674.039] [3188:3188] [DMG] InvokeRequests = + [1721131674.039] [3188:3188] [DMG] [ + [1721131674.039] [3188:3188] [DMG] CommandDataIB = + [1721131674.039] [3188:3188] [DMG] { + [1721131674.039] [3188:3188] [DMG] CommandPathIB = + [1721131674.039] [3188:3188] [DMG] { + [1721131674.039] [3188:3188] [DMG] EndpointId = 0x1, + [1721131674.039] [3188:3188] [DMG] ClusterId = 0x8, + [1721131674.039] [3188:3188] [DMG] CommandId = 0x5, + [1721131674.039] [3188:3188] [DMG] }, + [1721131674.039] [3188:3188] [DMG] + [1721131674.039] [3188:3188] [DMG] CommandFields = + [1721131674.039] [3188:3188] [DMG] { + [1721131674.039] [3188:3188] [DMG] 0x0 = 1 (unsigned), + [1721131674.039] [3188:3188] [DMG] 0x1 = 5 (unsigned), + [1721131674.039] [3188:3188] [DMG] 0x2 = 0 (unsigned), + [1721131674.039] [3188:3188] [DMG] 0x3 = 0 (unsigned), + [1721131674.040] [3188:3188] [DMG] }, + [1721131674.040] [3188:3188] [DMG] }, + [1721131674.040] [3188:3188] [DMG] + [1721131674.040] [3188:3188] [DMG] ], + [1721131674.040] [3188:3188] [DMG] + [1721131674.040] [3188:3188] [DMG] InteractionModelRevision = 11 + [1721131674.040] [3188:3188] [DMG] }, + [1721131674.040] [3188:3188] [DMG] AccessControl: checking f=1 a=c s=0x000000000001B669 t= c=0x0000_0008 e=1 p=o + [1721131674.040] [3188:3188] [DMG] AccessControl: allowed + [1721131674.040] [3188:3188] [DMG] Received command for Endpoint=1 Cluster=0x0000_0008 Command=0x0000_0005 + [1721131674.040] [3188:3188] [ZCL] RX level-control: MOVE_WITH_ON_OFF 1 5 0 0 + [1721131674.040] [3188:3188] [ZCL] Setting on/off to OFF due to level change + [1721131674.040] [3188:3188] [ZCL] Endpoint 1 On/off already set to new value + + + + + ./chip-tool levelcontrol step-with-on-off 1 15 123 0 0 1 1 + on TH (all-clusters-app) log. Verify the step-with-on-off command is received successfully. + + + [1721131696.543] [3188:3188] [EM] >>> [E:11849r S:12016 M:155492710] (S) Msg RX from 1:000000000001B669 [AD70] --- Type 0001:08 (IM:InvokeCommandRequest) + [1721131696.543] [3188:3188] [EM] Handling via exchange: 11849r, Delegate: 0xaaaac95ff3b0 + [1721131696.543] [3188:3188] [DMG] InvokeRequestMessage = + [1721131696.543] [3188:3188] [DMG] { + [1721131696.543] [3188:3188] [DMG] suppressResponse = false, + [1721131696.543] [3188:3188] [DMG] timedRequest = false, + [1721131696.543] [3188:3188] [DMG] InvokeRequests = + [1721131696.543] [3188:3188] [DMG] [ + [1721131696.543] [3188:3188] [DMG] CommandDataIB = + [1721131696.543] [3188:3188] [DMG] { + [1721131696.543] [3188:3188] [DMG] CommandPathIB = + [1721131696.543] [3188:3188] [DMG] { + [1721131696.543] [3188:3188] [DMG] EndpointId = 0x1, + [1721131696.543] [3188:3188] [DMG] ClusterId = 0x8, + [1721131696.543] [3188:3188] [DMG] CommandId = 0x6, + [1721131696.543] [3188:3188] [DMG] }, + [1721131696.544] [3188:3188] [DMG] + [1721131696.544] [3188:3188] [DMG] CommandFields = + [1721131696.544] [3188:3188] [DMG] { + [1721131696.544] [3188:3188] [DMG] 0x0 = 1 (unsigned), + [1721131696.544] [3188:3188] [DMG] 0x1 = 15 (unsigned), + [1721131696.544] [3188:3188] [DMG] 0x2 = 123 (unsigned), + [1721131696.544] [3188:3188] [DMG] 0x3 = 0 (unsigned), + [1721131696.544] [3188:3188] [DMG] 0x4 = 0 (unsigned), + [1721131696.544] [3188:3188] [DMG] }, + [1721131696.544] [3188:3188] [DMG] }, + [1721131696.544] [3188:3188] [DMG] + [1721131696.544] [3188:3188] [DMG] ], + [1721131696.544] [3188:3188] [DMG] + [1721131696.544] [3188:3188] [DMG] InteractionModelRevision = 11 + [1721131696.544] [3188:3188] [DMG] }, + [1721131696.544] [3188:3188] [DMG] AccessControl: checking f=1 a=c s=0x000000000001B669 t= c=0x0000_0008 e=1 p=o + [1721131696.544] [3188:3188] [DMG] AccessControl: allowed + [1721131696.544] [3188:3188] [DMG] Received command for Endpoint=1 Cluster=0x0000_0008 Command=0x0000_0006 + [1721131696.544] [3188:3188] [ZCL] RX level-control: STEP_WITH_ON_OFF 1 f 7b 0 0 + [1721131696.544] [3188:3188] [ZCL] Setting on/off to OFF due to level change + [1721131696.544] [3188:3188] [ZCL] Endpoint 1 On/off already set to new value + [1721131696.544] [3188:3188] [DMG] Command handler moving to [NewRespons] + + + ./chip-tool levelcontrol stop-with-on-off 0 0 1 1 + on TH (all-clusters-app) log. Verify the stop-with-on-off command is received successfully. + + [1721131714.456] [3188:3188] [EM] >>> [E:55346r S:12017 M:234822330] (S) Msg RX from 1:000000000001B669 [AD70] --- Type 0001:08 (IM:InvokeCommandRequest) + [1721131714.456] [3188:3188] [EM] Handling via exchange: 55346r, Delegate: 0xaaaac95ff3b0 + [1721131714.457] [3188:3188] [DMG] InvokeRequestMessage = + [1721131714.457] [3188:3188] [DMG] { + [1721131714.457] [3188:3188] [DMG] suppressResponse = false, + [1721131714.457] [3188:3188] [DMG] timedRequest = false, + [1721131714.457] [3188:3188] [DMG] InvokeRequests = + [1721131714.457] [3188:3188] [DMG] [ + [1721131714.457] [3188:3188] [DMG] CommandDataIB = + [1721131714.457] [3188:3188] [DMG] { + [1721131714.457] [3188:3188] [DMG] CommandPathIB = + [1721131714.457] [3188:3188] [DMG] { + [1721131714.457] [3188:3188] [DMG] EndpointId = 0x1, + [1721131714.457] [3188:3188] [DMG] ClusterId = 0x8, + [1721131714.457] [3188:3188] [DMG] CommandId = 0x7, + [1721131714.457] [3188:3188] [DMG] }, + [1721131714.457] [3188:3188] [DMG] + [1721131714.457] [3188:3188] [DMG] CommandFields = + [1721131714.457] [3188:3188] [DMG] { + [1721131714.457] [3188:3188] [DMG] 0x0 = 0 (unsigned), + [1721131714.457] [3188:3188] [DMG] 0x1 = 0 (unsigned), + [1721131714.457] [3188:3188] [DMG] }, + [1721131714.457] [3188:3188] [DMG] }, + [1721131714.457] [3188:3188] [DMG] + [1721131714.457] [3188:3188] [DMG] ], + [1721131714.457] [3188:3188] [DMG] + [1721131714.457] [3188:3188] [DMG] InteractionModelRevision = 11 + [1721131714.457] [3188:3188] [DMG] }, + [1721131714.457] [3188:3188] [DMG] AccessControl: checking f=1 a=c s=0x000000000001B669 t= c=0x0000_0008 e=1 p=o + [1721131714.457] [3188:3188] [DMG] AccessControl: allowed + [1721131714.457] [3188:3188] [DMG] Received command for Endpoint=1 Cluster=0x0000_0008 Command=0x0000_0007 + [1721131714.457] [3188:3188] [ZCL] RX level-control: STOP_WITH_ON_OFF + [1721131714.457] [3188:3188] [DMG] Command handler moving to [NewRespons] + [1721131714.458] [3188:3188] [DMG] Command handler moving to [ Preparing] + disabled: true diff --git a/src/app/tests/suites/certification/Test_TC_LVL_9_1.yaml b/src/app/tests/suites/certification/Test_TC_LVL_9_1.yaml index f341f9e1dc1087..6e63953d262143 100644 --- a/src/app/tests/suites/certification/Test_TC_LVL_9_1.yaml +++ b/src/app/tests/suites/certification/Test_TC_LVL_9_1.yaml @@ -58,9 +58,9 @@ tests: } - label: - "Step 0b: TH binds GroupIds 0x0001 and 0x0002 with GroupKeySetID - 0x01a1 in the GroupKeyMap attribute list on GroupKeyManagement cluster - by writing the GroupKeyMap attribute with two entries as follows:" + "Step 0b: TH binds GroupIds 0x0001 with GroupKeySetID 0x01a1 in the + GroupKeyMap attribute list on GroupKeyManagement cluster by writing + the GroupKeyMap attribute with two entries as follows:" cluster: "Group Key Management" endpoint: 0 command: "writeAttribute" @@ -122,8 +122,17 @@ tests: - name: "SceneList" value: [] + - label: "Step 2a: TH reads the MinLevel attribute from the DUT" + cluster: "Level Control" + command: "readAttribute" + attribute: "MinLevel" + response: + saveAs: MinLevelValue + constraints: + type: int8u + - label: - "Step 2a: TH sends a MoveToLevel command to DUT, with Level =0 and + "Step 2b: TH sends a MoveToLevel command to DUT, with Level =0 and TransitionTime =0 (immediate)" cluster: "Level Control" command: "MoveToLevel" @@ -138,15 +147,6 @@ tests: - name: "OptionsOverride" value: 1 - - label: "Step 2b: TH reads the MinLevel attribute from the DUT" - cluster: "Level Control" - command: "readAttribute" - attribute: "MinLevel" - response: - saveAs: MinLevelValue - constraints: - type: int8u - - label: "Step 2c: TH reads the CurrentLevel attribute from DUT" cluster: "Level Control" command: "readAttribute" From 3b83878fc839320a97460a722138068bd18c9225 Mon Sep 17 00:00:00 2001 From: Junior Martinez <67972863+jmartinez-silabs@users.noreply.github.com> Date: Tue, 3 Sep 2024 18:43:17 -0400 Subject: [PATCH 06/15] Update Quiet reporting conditions for LVL and CC remaining time attribute (#35224) * Update Quiet reporting conditions for LVL and CC remaining time attribute * Restyled by clang-format * Fix/cleaup of startUpColorTempCommand * Add missing SetQuietReportRemainingTime for moveToColorTemp command. Add remainingTime Report condition on command invoke. Fix clang-tidy * Update src/app/clusters/color-control-server/color-control-server.cpp Co-authored-by: Boris Zbarsky --------- Co-authored-by: Restyled.io Co-authored-by: Boris Zbarsky --- .../color-control-server.cpp | 153 ++++++++++-------- .../color-control-server.h | 20 +-- .../clusters/level-control/level-control.cpp | 70 ++++---- 3 files changed, 130 insertions(+), 113 deletions(-) diff --git a/src/app/clusters/color-control-server/color-control-server.cpp b/src/app/clusters/color-control-server/color-control-server.cpp index 05553525258d23..2a858884220fc9 100644 --- a/src/app/clusters/color-control-server/color-control-server.cpp +++ b/src/app/clusters/color-control-server/color-control-server.cpp @@ -1040,7 +1040,7 @@ void ColorControlServer::startColorLoop(EndpointId endpoint, uint8_t startFromSt colorHueTransitionState->transitionTime = MAX_INT16U_VALUE; colorHueTransitionState->endpoint = endpoint; - SetQuietReportRemainingTime(endpoint, MAX_INT16U_VALUE); + SetQuietReportRemainingTime(endpoint, MAX_INT16U_VALUE, true /* isNewTransition */); scheduleTimerCallbackMs(configureHSVEventControl(endpoint), TRANSITION_UPDATE_TIME_MS.count()); } @@ -1091,7 +1091,10 @@ void ColorControlServer::SetHSVRemainingTime(chip::EndpointId endpoint) // When the hue transition is loop, RemainingTime stays at MAX_INT16 if (hueTransitionState->repeat == false) { - SetQuietReportRemainingTime(endpoint, max(hueTransitionState->timeRemaining, saturationTransitionState->timeRemaining)); + bool hsvTransitionStart = (hueTransitionState->stepsRemaining == hueTransitionState->stepsTotal) || + (saturationTransitionState->stepsRemaining == saturationTransitionState->stepsTotal); + SetQuietReportRemainingTime(endpoint, max(hueTransitionState->timeRemaining, saturationTransitionState->timeRemaining), + hsvTransitionStart); } } @@ -1484,7 +1487,7 @@ bool ColorControlServer::moveHueCommand(app::CommandHandler * commandObj, const colorHueTransitionState->repeat = true; // hue movement can last forever. Indicate this with a remaining time of maxint - SetQuietReportRemainingTime(endpoint, MAX_INT16U_VALUE); + SetQuietReportRemainingTime(endpoint, MAX_INT16U_VALUE, true /* isNewTransition */); // kick off the state machine: scheduleTimerCallbackMs(configureHSVEventControl(endpoint), TRANSITION_UPDATE_TIME_MS.count()); @@ -2052,7 +2055,7 @@ bool ColorControlServer::colorLoopCommand(app::CommandHandler * commandObj, cons uint16_t storedEnhancedHue = 0; Attributes::ColorLoopStoredEnhancedHue::Get(endpoint, &storedEnhancedHue); MarkAttributeDirty markDirty = - SetQuietReportAttribute(quietEnhancedHue[epIndex], storedEnhancedHue, true /*isStartOrEndOfTransition*/, 0); + SetQuietReportAttribute(quietEnhancedHue[epIndex], storedEnhancedHue, true /*isEndOfTransition*/, 0); Attributes::EnhancedCurrentHue::Set(endpoint, quietEnhancedHue[epIndex].value().Value(), markDirty); } else @@ -2094,10 +2097,6 @@ void ColorControlServer::updateHueSatCommand(EndpointId endpoint) uint16_t previousSaturation = colorSaturationTransitionState->currentValue; uint16_t previousEnhancedhue = colorHueTransitionState->currentEnhancedHue; - bool isHueTansitionStart = (colorHueTransitionState->stepsRemaining == colorHueTransitionState->stepsTotal); - bool isSaturationTransitionStart = - (colorSaturationTransitionState->stepsRemaining == colorSaturationTransitionState->stepsTotal); - bool isHueTansitionDone = computeNewHueValue(colorHueTransitionState); bool isSaturationTransitionDone = computeNewColor16uValue(colorSaturationTransitionState); @@ -2117,7 +2116,7 @@ void ColorControlServer::updateHueSatCommand(EndpointId endpoint) if (colorHueTransitionState->isEnhancedHue) { markDirty = SetQuietReportAttribute(quietEnhancedHue[epIndex], colorHueTransitionState->currentEnhancedHue, - (isHueTansitionStart || isHueTansitionDone), colorHueTransitionState->transitionTime); + isHueTansitionDone, colorHueTransitionState->transitionTime); Attributes::EnhancedCurrentHue::Set(endpoint, quietEnhancedHue[epIndex].value().Value(), markDirty); currentHue = static_cast(colorHueTransitionState->currentEnhancedHue >> 8); @@ -2135,8 +2134,7 @@ void ColorControlServer::updateHueSatCommand(EndpointId endpoint) } } - markDirty = SetQuietReportAttribute(quietHue[epIndex], currentHue, (isHueTansitionStart || isHueTansitionDone), - colorHueTransitionState->transitionTime); + markDirty = SetQuietReportAttribute(quietHue[epIndex], currentHue, isHueTansitionDone, colorHueTransitionState->transitionTime); Attributes::CurrentHue::Set(endpoint, quietHue[epIndex].value().Value(), markDirty); if (previousSaturation != colorSaturationTransitionState->currentValue) @@ -2145,8 +2143,7 @@ void ColorControlServer::updateHueSatCommand(EndpointId endpoint) } markDirty = SetQuietReportAttribute(quietSaturation[epIndex], colorSaturationTransitionState->currentValue, - (isSaturationTransitionStart || isSaturationTransitionDone), - colorSaturationTransitionState->transitionTime); + isSaturationTransitionDone, colorSaturationTransitionState->transitionTime); Attributes::CurrentSaturation::Set(endpoint, quietSaturation[epIndex].value().Value(), markDirty); computePwmFromHsv(endpoint); @@ -2298,7 +2295,7 @@ Status ColorControlServer::moveToColor(uint16_t colorX, uint16_t colorY, uint16_ colorYTransitionState->lowLimit = MIN_CIE_XY_VALUE; colorYTransitionState->highLimit = MAX_CIE_XY_VALUE; - SetQuietReportRemainingTime(endpoint, transitionTime); + SetQuietReportRemainingTime(endpoint, transitionTime, true /* isNewTransition */); // kick off the state machine: scheduleTimerCallbackMs(configureXYEventControl(endpoint), transitionTime ? TRANSITION_UPDATE_TIME_MS.count() : 0); @@ -2405,7 +2402,7 @@ bool ColorControlServer::moveColorCommand(app::CommandHandler * commandObj, cons colorYTransitionState->lowLimit = MIN_CIE_XY_VALUE; colorYTransitionState->highLimit = MAX_CIE_XY_VALUE; - SetQuietReportRemainingTime(endpoint, max(transitionTimeX, transitionTimeY)); + SetQuietReportRemainingTime(endpoint, max(transitionTimeX, transitionTimeY), true /* isNewTransition */); // kick off the state machine: scheduleTimerCallbackMs(configureXYEventControl(endpoint), TRANSITION_UPDATE_TIME_MS.count()); @@ -2485,7 +2482,7 @@ bool ColorControlServer::stepColorCommand(app::CommandHandler * commandObj, cons colorYTransitionState->lowLimit = MIN_CIE_XY_VALUE; colorYTransitionState->highLimit = MAX_CIE_XY_VALUE; - SetQuietReportRemainingTime(endpoint, transitionTime); + SetQuietReportRemainingTime(endpoint, transitionTime, true /* isNewTransition */); // kick off the state machine: scheduleTimerCallbackMs(configureXYEventControl(endpoint), transitionTime ? TRANSITION_UPDATE_TIME_MS.count() : 0); @@ -2521,15 +2518,10 @@ void ColorControlServer::updateXYCommand(EndpointId endpoint) scheduleTimerCallbackMs(configureXYEventControl(endpoint), TRANSITION_UPDATE_TIME_MS.count()); } - bool isXTransitionStart = (colorXTransitionState->stepsRemaining == colorXTransitionState->stepsTotal); - bool isYTransitionStart = (colorYTransitionState->stepsRemaining == colorYTransitionState->stepsTotal); - - MarkAttributeDirty markXDirty = - SetQuietReportAttribute(quietColorX[epIndex], colorXTransitionState->currentValue, - (isXTransitionStart || isXTransitionDone), colorXTransitionState->transitionTime); - MarkAttributeDirty markYDirty = - SetQuietReportAttribute(quietColorY[epIndex], colorYTransitionState->currentValue, - (isYTransitionStart || isYTransitionDone), colorYTransitionState->transitionTime); + MarkAttributeDirty markXDirty = SetQuietReportAttribute(quietColorX[epIndex], colorXTransitionState->currentValue, + isXTransitionDone, colorXTransitionState->transitionTime); + MarkAttributeDirty markYDirty = SetQuietReportAttribute(quietColorY[epIndex], colorYTransitionState->currentValue, + isYTransitionDone, colorYTransitionState->transitionTime); Attributes::CurrentX::Set(endpoint, quietColorX[epIndex].value().Value(), markXDirty); Attributes::CurrentY::Set(endpoint, quietColorY[epIndex].value().Value(), markYDirty); @@ -2623,6 +2615,8 @@ Status ColorControlServer::moveToColorTemp(EndpointId aEndpoint, uint16_t colorT colorTempTransitionState->lowLimit = temperatureMin; colorTempTransitionState->highLimit = temperatureMax; + SetQuietReportRemainingTime(endpoint, transitionTime, true /* isNewTransition */); + // kick off the state machine scheduleTimerCallbackMs(configureTempEventControl(endpoint), transitionTime ? TRANSITION_UPDATE_TIME_MS.count() : 0); return Status::Success; @@ -2687,36 +2681,32 @@ void ColorControlServer::startUpColorTempCommand(EndpointId endpoint) if (status == Status::Success && !startUpColorTemp.IsNull()) { - uint16_t updatedColorTemp = MAX_TEMPERATURE_VALUE; - status = Attributes::ColorTemperatureMireds::Get(endpoint, &updatedColorTemp); + uint16_t tempPhysicalMin = MIN_TEMPERATURE_VALUE; + Attributes::ColorTempPhysicalMinMireds::Get(endpoint, &tempPhysicalMin); + // Avoid potential divide-by-zero in future Kelvin conversions. + tempPhysicalMin = std::max(static_cast(1u), tempPhysicalMin); - if (status == Status::Success) - { - uint16_t tempPhysicalMin = MIN_TEMPERATURE_VALUE; - Attributes::ColorTempPhysicalMinMireds::Get(endpoint, &tempPhysicalMin); - // Avoid potential divide-by-zero in future Kelvin conversions. - tempPhysicalMin = std::max(static_cast(1u), tempPhysicalMin); + uint16_t tempPhysicalMax = MAX_TEMPERATURE_VALUE; + Attributes::ColorTempPhysicalMaxMireds::Get(endpoint, &tempPhysicalMax); - uint16_t tempPhysicalMax = MAX_TEMPERATURE_VALUE; - Attributes::ColorTempPhysicalMaxMireds::Get(endpoint, &tempPhysicalMax); + if (tempPhysicalMin <= startUpColorTemp.Value() && startUpColorTemp.Value() <= tempPhysicalMax) + { + // Apply valid startup color temp value that is within physical limits of device. + // Otherwise, the startup value is outside the device's supported range, and the + // existing setting of ColorTemp attribute will be left unchanged (i.e., treated as + // if startup color temp was set to null). + uint16_t epIndex = getEndpointIndex(endpoint); + MarkAttributeDirty markDirty = SetQuietReportAttribute(quietTemperatureMireds[epIndex], startUpColorTemp.Value(), + false /* isEndOfTransition */, 0); + status = Attributes::ColorTemperatureMireds::Set(endpoint, quietTemperatureMireds[epIndex].value().Value(), markDirty); - if (tempPhysicalMin <= startUpColorTemp.Value() && startUpColorTemp.Value() <= tempPhysicalMax) + if (status == Status::Success) { - // Apply valid startup color temp value that is within physical limits of device. - // Otherwise, the startup value is outside the device's supported range, and the - // existing setting of ColorTemp attribute will be left unchanged (i.e., treated as - // if startup color temp was set to null). - updatedColorTemp = startUpColorTemp.Value(); - status = Attributes::ColorTemperatureMireds::Set(endpoint, updatedColorTemp); - - if (status == Status::Success) - { - // Set ColorMode attributes to reflect ColorTemperature. - auto updateColorMode = ColorModeEnum::kColorTemperatureMireds; - Attributes::ColorMode::Set(endpoint, updateColorMode); + // Set ColorMode attributes to reflect ColorTemperature. + auto updateColorMode = ColorModeEnum::kColorTemperatureMireds; + Attributes::ColorMode::Set(endpoint, updateColorMode); - Attributes::EnhancedColorMode::Set(endpoint, static_cast(updateColorMode)); - } + Attributes::EnhancedColorMode::Set(endpoint, static_cast(updateColorMode)); } } } @@ -2729,7 +2719,8 @@ void ColorControlServer::startUpColorTempCommand(EndpointId endpoint) */ void ColorControlServer::updateTempCommand(EndpointId endpoint) { - Color16uTransitionState * colorTempTransitionState = getTempTransitionState(endpoint); + uint16_t epIndex = getEndpointIndex(endpoint); + Color16uTransitionState * colorTempTransitionState = getTempTransitionStateByIndex(epIndex); bool isColorTempTransitionDone; isColorTempTransitionDone = computeNewColor16uValue(colorTempTransitionState); @@ -2763,7 +2754,9 @@ void ColorControlServer::updateTempCommand(EndpointId endpoint) scheduleTimerCallbackMs(configureTempEventControl(endpoint), TRANSITION_UPDATE_TIME_MS.count()); } - Attributes::ColorTemperatureMireds::Set(endpoint, colorTempTransitionState->currentValue); + MarkAttributeDirty markDirty = SetQuietReportAttribute(quietTemperatureMireds[epIndex], colorTempTransitionState->currentValue, + isColorTempTransitionDone, colorTempTransitionState->timeRemaining); + Attributes::ColorTemperatureMireds::Set(endpoint, quietTemperatureMireds[epIndex].value().Value(), markDirty); ChipLogProgress(Zcl, "Color Temperature %d", colorTempTransitionState->currentValue); @@ -2882,7 +2875,7 @@ bool ColorControlServer::moveColorTempCommand(app::CommandHandler * commandObj, colorTempTransitionState->lowLimit = colorTemperatureMinimum; colorTempTransitionState->highLimit = colorTemperatureMaximum; - SetQuietReportRemainingTime(endpoint, transitionTime); + SetQuietReportRemainingTime(endpoint, transitionTime, true /* isNewTransition */); // kick off the state machine: scheduleTimerCallbackMs(configureTempEventControl(endpoint), TRANSITION_UPDATE_TIME_MS.count()); @@ -3005,7 +2998,7 @@ bool ColorControlServer::stepColorTempCommand(app::CommandHandler * commandObj, colorTempTransitionState->lowLimit = colorTemperatureMinimum; colorTempTransitionState->highLimit = colorTemperatureMaximum; - SetQuietReportRemainingTime(endpoint, transitionTime); + SetQuietReportRemainingTime(endpoint, transitionTime, true /* isNewTransition */); // kick off the state machine: scheduleTimerCallbackMs(configureTempEventControl(endpoint), transitionTime ? TRANSITION_UPDATE_TIME_MS.count() : 0); @@ -3103,7 +3096,6 @@ void ColorControlServer::levelControlColorTempChangeCommand(EndpointId endpoint) * Utility function used to update a color control attribute which has the quiet reporting quality. * matching the following report conditions: * - At most once per second, or - * - At the start of the movement/transition, or * - At the end of the movement/transition, or * - When it changes from null to any other value and vice versa. (Implicit to the QuieterReportingAttribute class) * @@ -3114,20 +3106,20 @@ void ColorControlServer::levelControlColorTempChangeCommand(EndpointId endpoint) * * @param quietReporter: The QuieterReportingAttribute object for the attribute to update. * @param newValue: Value to update the attribute with - * @param isStartOrEndOfTransition: Boolean that indicatse whether the update is occurring at the start or end of a level transition + * @param isEndOfTransition: Boolean that indicates whether the update is occurring at the end of a color transition * @return MarkAttributeDirty::kYes when the attribute must be marked dirty and be reported. MarkAttributeDirty::kNo when * no report is needed. */ template MarkAttributeDirty ColorControlServer::SetQuietReportAttribute(QuieterReportingAttribute & quietReporter, V newValue, - bool isStartOrEndOfTransition, uint16_t transitionTime) + bool isEndOfTransition, uint16_t transitionTime) { AttributeDirtyState dirtyState; auto now = System::SystemClock().GetMonotonicTimestamp(); - if (isStartOrEndOfTransition) + if (isEndOfTransition) { - // At the start or end of the movement/transition we must report if the value changed + // At the end of the movement/transition we must report if the value changed auto predicate = [](const typename QuieterReportingAttribute::SufficientChangePredicateCandidate &) -> bool { return true; }; @@ -3157,23 +3149,42 @@ MarkAttributeDirty ColorControlServer::SetQuietReportAttribute(QuieterReportingA * @brief * Function used to set the remaining time based on quiet reporting conditions. * It will update the attribute storage and report the attribute if it is determined dirty. - * The condition on which the attribute must be reported are defined by the set QuieterReportingPolicyFlags - * of the quietRemainingTime object and the implicit conditions of the QuieterReportingAttribute class + * The conditions on which the attribute must be reported are: + * - When it changes from 0 to any value higher than 10, or + * - When it changes, with a delta larger than 10, caused by the invoke of a command, or + * - When it changes to 0. * * @param endpoint: Endpoint of the RemainingTime attribute to set * @param newRemainingTime: Value to update the RemainingTime attribute with * @return Success in setting the attribute value or the IM error code for the failure. */ -Status ColorControlServer::SetQuietReportRemainingTime(EndpointId endpoint, uint16_t newRemainingTime) -{ - uint16_t epIndex = getEndpointIndex(endpoint); - auto markDirty = MarkAttributeDirty::kNo; - auto now = System::SystemClock().GetMonotonicTimestamp(); - // Establish the quiet report condition for the RemainingTime Attribute - // The quiet report is by the previously set policies : - // - kMarkDirtyOnChangeToFromZero : When the value changes from 0 to any other value and vice versa, or - // - kMarkDirtyOnIncrement : When the value increases. - if (quietRemainingTime[epIndex].SetValue(newRemainingTime, now) == AttributeDirtyState::kMustReport) +Status ColorControlServer::SetQuietReportRemainingTime(EndpointId endpoint, uint16_t newRemainingTime, bool isNewTransition) +{ + uint16_t epIndex = getEndpointIndex(endpoint); + uint16_t lastRemainingTime = quietRemainingTime[epIndex].value().ValueOr(0); + auto markDirty = MarkAttributeDirty::kNo; + auto now = System::SystemClock().GetMonotonicTimestamp(); + + auto predicate = + [isNewTransition, lastRemainingTime]( + const typename QuieterReportingAttribute::SufficientChangePredicateCandidate & candidate) -> bool { + constexpr uint16_t reportDelta = 10; + bool isDirty = false; + if (candidate.newValue.Value() == 0 || (candidate.lastDirtyValue.Value() == 0 && candidate.newValue.Value() > reportDelta)) + { + isDirty = true; + } + else if (isNewTransition && + (candidate.newValue.Value() > static_cast(lastRemainingTime + reportDelta) || + static_cast(candidate.newValue.Value() + reportDelta) < lastRemainingTime || + candidate.newValue.Value() > static_cast(candidate.lastDirtyValue.Value() + reportDelta))) + { + isDirty = true; + } + return isDirty; + }; + + if (quietRemainingTime[epIndex].SetValue(newRemainingTime, now, predicate) == AttributeDirtyState::kMustReport) { markDirty = MarkAttributeDirty::kYes; } diff --git a/src/app/clusters/color-control-server/color-control-server.h b/src/app/clusters/color-control-server/color-control-server.h index 88343e7cb0f4af..422c7f592f8b33 100644 --- a/src/app/clusters/color-control-server/color-control-server.h +++ b/src/app/clusters/color-control-server/color-control-server.h @@ -203,27 +203,16 @@ class ColorControlServer template chip::app::MarkAttributeDirty SetQuietReportAttribute(chip::app::QuieterReportingAttribute & quietReporter, V newValue, - bool isStartOrEndOfTransition, uint16_t transitionTime); - chip::Protocols::InteractionModel::Status SetQuietReportRemainingTime(chip::EndpointId endpoint, uint16_t newRemainingTime); + bool isEndOfTransition, uint16_t transitionTime); + chip::Protocols::InteractionModel::Status SetQuietReportRemainingTime(chip::EndpointId endpoint, uint16_t newRemainingTime, + bool isNewTransition = false); private: /********************************************************** * Functions Definitions *********************************************************/ - ColorControlServer() - { - for (size_t i = 0; i < kColorControlClusterServerMaxEndpointCount; i++) - { - // Set the quiet report policies for the RemaininTime Attribute on all endpoint - // - kMarkDirtyOnChangeToFromZero : When the value changes from 0 to any other value and vice versa, or - // - kMarkDirtyOnIncrement : When the value increases. - quietRemainingTime[i] - .policy() - .Set(chip::app::QuieterReportingPolicyEnum::kMarkDirtyOnIncrement) - .Set(chip::app::QuieterReportingPolicyEnum::kMarkDirtyOnChangeToFromZero); - } - } + ColorControlServer() {} bool shouldExecuteIfOff(chip::EndpointId endpoint, chip::BitMask optionMask, chip::BitMask optionOverride); @@ -312,6 +301,7 @@ class ColorControlServer #ifdef MATTER_DM_PLUGIN_COLOR_CONTROL_SERVER_TEMP Color16uTransitionState colorTempTransitionStates[kColorControlClusterServerMaxEndpointCount]; + chip::app::QuieterReportingAttribute quietTemperatureMireds[kColorControlClusterServerMaxEndpointCount]; #endif // MATTER_DM_PLUGIN_COLOR_CONTROL_SERVER_TEMP EmberEventControl eventControls[kColorControlClusterServerMaxEndpointCount]; diff --git a/src/app/clusters/level-control/level-control.cpp b/src/app/clusters/level-control/level-control.cpp index 231a542c2ef1b4..c176d6f6b17bbb 100644 --- a/src/app/clusters/level-control/level-control.cpp +++ b/src/app/clusters/level-control/level-control.cpp @@ -121,12 +121,12 @@ static void stopHandler(CommandHandler * commandObj, const ConcreteCommandPath & chip::Optional> optionsMask, chip::Optional> optionsOverride); static void setOnOffValue(EndpointId endpoint, bool onOff); -static void writeRemainingTime(EndpointId endpoint, uint16_t remainingTimeMs); +static void writeRemainingTime(EndpointId endpoint, uint16_t remainingTimeMs, bool isNewTransition = false); static bool shouldExecuteIfOff(EndpointId endpoint, CommandId commandId, chip::Optional> optionsMask, chip::Optional> optionsOverride); static Status SetCurrentLevelQuietReport(EndpointId endpoint, EmberAfLevelControlState * state, - DataModel::Nullable newValue, bool isStartOrEndOfTransition); + DataModel::Nullable newValue, bool isEndOfTransition); #if defined(MATTER_DM_PLUGIN_SCENES_MANAGEMENT) && CHIP_CONFIG_SCENES_USE_DEFAULT_HANDLERS class DefaultLevelControlSceneHandler : public scenes::DefaultSceneHandlerImpl @@ -372,23 +372,22 @@ static void reallyUpdateCoupledColorTemp(EndpointId endpoint) * while respecting its defined quiet reporting quality: * The attribute will be reported: * - At most once per second, or - * - At the start of the movement/transition, or * - At the end of the movement/transition, or * - When it changes from null to any other value and vice versa. * * @param endpoint: endpoint on which the currentLevel attribute must be updated. * @param state: LevelControlState struct of this given endpoint. * @param newValue: Value to update the attribute with - * @param isStartOrEndOfTransition: Boolean that indicate whether the update is occuring at the start or end of a level transition + * @param isEndOfTransition: Boolean that indicate whether the update is occuring at the end of a level transition * @return Success in setting the attribute value or the IM error code for the failure. */ static Status SetCurrentLevelQuietReport(EndpointId endpoint, EmberAfLevelControlState * state, - DataModel::Nullable newValue, bool isStartOrEndOfTransition) + DataModel::Nullable newValue, bool isEndOfTransition) { AttributeDirtyState dirtyState; auto now = System::SystemClock().GetMonotonicTimestamp(); - if (isStartOrEndOfTransition) + if (isEndOfTransition) { // At the start or end of the movement/transition we must report auto predicate = [](const decltype(state->quietCurrentLevel)::SufficientChangePredicateCandidate &) -> bool { @@ -467,7 +466,7 @@ void emberAfLevelControlClusterServerTickCallback(EndpointId endpoint) // Are we at the requested level? isTransitionEnd = (currentLevel.Value() == state->moveToLevel); - status = SetCurrentLevelQuietReport(endpoint, state, currentLevel, (isTransitionStart || isTransitionEnd)); + status = SetCurrentLevelQuietReport(endpoint, state, currentLevel, isTransitionEnd); if (status != Status::Success) { ChipLogProgress(Zcl, "ERR: writing current level %x", to_underlying(status)); @@ -506,12 +505,12 @@ void emberAfLevelControlClusterServerTickCallback(EndpointId endpoint) else { state->callbackSchedule.runTime = System::SystemClock().GetMonotonicTimestamp() - callbackStartTimestamp; - writeRemainingTime(endpoint, static_cast(state->transitionTimeMs - state->elapsedTimeMs)); + writeRemainingTime(endpoint, static_cast(state->transitionTimeMs - state->elapsedTimeMs), isTransitionStart); scheduleTimerCallbackMs(endpoint, computeCallbackWaitTimeMs(state->callbackSchedule, state->eventDurationMs)); } } -static void writeRemainingTime(EndpointId endpoint, uint16_t remainingTimeMs) +static void writeRemainingTime(EndpointId endpoint, uint16_t remainingTimeMs, bool isNewTransition) { #ifndef IGNORE_LEVEL_CONTROL_CLUSTER_LEVEL_CONTROL_REMAINING_TIME if (emberAfContainsAttribute(endpoint, LevelControl::Id, LevelControl::Attributes::RemainingTime::Id)) @@ -531,16 +530,36 @@ static void writeRemainingTime(EndpointId endpoint, uint16_t remainingTimeMs) // // This is done to ensure that the attribute, in tenths of a second, only // goes to zero when the remaining time in milliseconds is actually zero. - uint16_t remainingTimeDs = static_cast((remainingTimeMs + 99) / 100); - auto markDirty = MarkAttributeDirty::kNo; - auto state = getState(endpoint); - auto now = System::SystemClock().GetMonotonicTimestamp(); - - // Establish the quiet report condition for the RemainingTime Attribute - // The quiet report is determined by the previously set policies: - // - kMarkDirtyOnChangeToFromZero : When the value changes from 0 to any other value and vice versa, or - // - kMarkDirtyOnIncrement : When the value increases. - if (state->quietRemainingTime.SetValue(remainingTimeDs, now) == AttributeDirtyState::kMustReport) + auto markDirty = MarkAttributeDirty::kNo; + auto state = getState(endpoint); + auto now = System::SystemClock().GetMonotonicTimestamp(); + uint16_t remainingTimeDs = static_cast((remainingTimeMs + 99) / 100); + uint16_t lastRemainingTime = state->quietRemainingTime.value().ValueOr(0); + + // RemainingTime Quiet report conditions: + // - When it changes to 0, or + // - When it changes from 0 to any value higher than 10, or + // - When it changes, with a delta larger than 10, caused by the invoke of a command. + auto predicate = [isNewTransition, lastRemainingTime]( + const decltype(state->quietRemainingTime)::SufficientChangePredicateCandidate & candidate) -> bool { + constexpr uint16_t reportDelta = 10; + bool isDirty = false; + if (candidate.newValue.Value() == 0 || + (candidate.lastDirtyValue.Value() == 0 && candidate.newValue.Value() > reportDelta)) + { + isDirty = true; + } + else if (isNewTransition && + (candidate.newValue.Value() > static_cast(lastRemainingTime + reportDelta) || + static_cast(candidate.newValue.Value() + reportDelta) < lastRemainingTime || + candidate.newValue.Value() > static_cast(candidate.lastDirtyValue.Value() + reportDelta))) + { + isDirty = true; + } + return isDirty; + }; + + if (state->quietRemainingTime.SetValue(remainingTimeDs, now, predicate) == AttributeDirtyState::kMustReport) { markDirty = MarkAttributeDirty::kYes; } @@ -1316,7 +1335,7 @@ static void stopHandler(CommandHandler * commandObj, const ConcreteCommandPath & // Cancel any currently active command. cancelEndpointTimerCallback(endpoint); - SetCurrentLevelQuietReport(endpoint, state, state->quietCurrentLevel.value(), true /*isStartOrEndOfTransition*/); + SetCurrentLevelQuietReport(endpoint, state, state->quietCurrentLevel.value(), true /*isEndOfTransition*/); writeRemainingTime(endpoint, 0); send_default_response: @@ -1410,7 +1429,7 @@ void emberAfOnOffClusterLevelControlEffectCallback(EndpointId endpoint, bool new { // If newValue is OnOff::Commands::On::Id... // "Set CurrentLevel to minimum level allowed for the device." - status = SetCurrentLevelQuietReport(endpoint, state, minimumLevelAllowedForTheDevice, true /*isStartOrEndOfTransition*/); + status = SetCurrentLevelQuietReport(endpoint, state, minimumLevelAllowedForTheDevice, false /*isEndOfTransition*/); if (status != Status::Success) { ChipLogProgress(Zcl, "ERR: reading current level %x", to_underlying(status)); @@ -1453,9 +1472,6 @@ void emberAfLevelControlClusterServerInitCallback(EndpointId endpoint) return; } - state->quietRemainingTime.policy() - .Set(QuieterReportingPolicyEnum::kMarkDirtyOnIncrement) - .Set(QuieterReportingPolicyEnum::kMarkDirtyOnChangeToFromZero); state->minLevel = MATTER_DM_PLUGIN_LEVEL_CONTROL_MINIMUM_LEVEL; state->maxLevel = MATTER_DM_PLUGIN_LEVEL_CONTROL_MAXIMUM_LEVEL; @@ -1528,18 +1544,18 @@ void emberAfLevelControlClusterServerInitCallback(EndpointId endpoint) } } // Otherwise Set the CurrentLevel attribute to its previous value which was already fetch above - SetCurrentLevelQuietReport(endpoint, state, currentLevel, true /*isStartOrEndOfTransition*/); + SetCurrentLevelQuietReport(endpoint, state, currentLevel, false /*isEndOfTransition*/); } } #endif // IGNORE_LEVEL_CONTROL_CLUSTER_START_UP_CURRENT_LEVEL // In any case, we make sure that the respects min/max if (currentLevel.IsNull() || currentLevel.Value() < state->minLevel) { - SetCurrentLevelQuietReport(endpoint, state, state->minLevel, true /*isStartOrEndOfTransition*/); + SetCurrentLevelQuietReport(endpoint, state, state->minLevel, false /*isEndOfTransition*/); } else if (currentLevel.Value() > state->maxLevel) { - SetCurrentLevelQuietReport(endpoint, state, state->maxLevel, true /*isStartOrEndOfTransition*/); + SetCurrentLevelQuietReport(endpoint, state, state->maxLevel, false /*isEndOfTransition*/); } } From 3de6f0478cd267b6aa71beebb1f8734c5d571f7e Mon Sep 17 00:00:00 2001 From: yunhanw-google Date: Tue, 3 Sep 2024 16:01:37 -0700 Subject: [PATCH 07/15] [ICD] refreshKey needs to be set with length (#35379) * refreshKey needs to be set with length * address comments --- src/app/icd/client/DefaultCheckInDelegate.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/icd/client/DefaultCheckInDelegate.cpp b/src/app/icd/client/DefaultCheckInDelegate.cpp index 2d4624b7837470..081b4be7bb7395 100644 --- a/src/app/icd/client/DefaultCheckInDelegate.cpp +++ b/src/app/icd/client/DefaultCheckInDelegate.cpp @@ -41,7 +41,8 @@ void DefaultCheckInDelegate::OnCheckInComplete(const ICDClientInfo & clientInfo) CHIP_ERROR DefaultCheckInDelegate::GenerateRefreshKey(RefreshKeySender::RefreshKeyBuffer & newKey) { - return Crypto::DRBG_get_bytes(newKey.Bytes(), newKey.Capacity()); + ReturnErrorOnFailure(Crypto::DRBG_get_bytes(newKey.Bytes(), newKey.Capacity())); + return newKey.SetLength(newKey.Capacity()); } RefreshKeySender * DefaultCheckInDelegate::OnKeyRefreshNeeded(ICDClientInfo & clientInfo, ICDClientStorage * clientStorage) From cacf69fca8024ed19245647ece4b1d87c8993368 Mon Sep 17 00:00:00 2001 From: Terence Hampson Date: Tue, 3 Sep 2024 19:46:30 -0400 Subject: [PATCH 08/15] fabric-admin add ability to grab uid from remote fabric sync device (#35377) --- examples/fabric-admin/BUILD.gn | 2 + .../device_manager/DeviceManager.h | 6 +- .../device_manager/DeviceSubscription.cpp | 11 +- .../device_manager/DeviceSynchronization.cpp | 120 +++++++++++--- .../device_manager/DeviceSynchronization.h | 23 ++- .../fabric-admin/device_manager/UidGetter.cpp | 146 ++++++++++++++++++ .../fabric-admin/device_manager/UidGetter.h | 64 ++++++++ 7 files changed, 343 insertions(+), 29 deletions(-) create mode 100644 examples/fabric-admin/device_manager/UidGetter.cpp create mode 100644 examples/fabric-admin/device_manager/UidGetter.h diff --git a/examples/fabric-admin/BUILD.gn b/examples/fabric-admin/BUILD.gn index 805fda1cb500c7..36ba7ec4e15d51 100644 --- a/examples/fabric-admin/BUILD.gn +++ b/examples/fabric-admin/BUILD.gn @@ -88,6 +88,8 @@ static_library("fabric-admin-utils") { "device_manager/DeviceSubscriptionManager.h", "device_manager/DeviceSynchronization.cpp", "device_manager/DeviceSynchronization.h", + "device_manager/UidGetter.cpp", + "device_manager/UidGetter.h", ] deps = [ "${chip_root}/src/app:events" ] diff --git a/examples/fabric-admin/device_manager/DeviceManager.h b/examples/fabric-admin/device_manager/DeviceManager.h index 22a3004aa1642b..5f617845f7dc76 100644 --- a/examples/fabric-admin/device_manager/DeviceManager.h +++ b/examples/fabric-admin/device_manager/DeviceManager.h @@ -171,6 +171,9 @@ class DeviceManager : public PairingDelegate void HandleCommandResponse(const chip::app::ConcreteCommandPath & path, chip::TLV::TLVReader & data); + Device * FindDeviceByEndpoint(chip::EndpointId endpointId); + Device * FindDeviceByNode(chip::NodeId nodeId); + private: friend DeviceManager & DeviceMgr(); @@ -206,9 +209,6 @@ class DeviceManager : public PairingDelegate bool mAutoSyncEnabled = false; bool mInitialized = false; uint64_t mRequestId = 0; - - Device * FindDeviceByEndpoint(chip::EndpointId endpointId); - Device * FindDeviceByNode(chip::NodeId nodeId); }; /** diff --git a/examples/fabric-admin/device_manager/DeviceSubscription.cpp b/examples/fabric-admin/device_manager/DeviceSubscription.cpp index eabbdda15dea15..dddb4f9f758291 100644 --- a/examples/fabric-admin/device_manager/DeviceSubscription.cpp +++ b/examples/fabric-admin/device_manager/DeviceSubscription.cpp @@ -207,10 +207,15 @@ CHIP_ERROR DeviceSubscription::StartSubscription(OnDoneCallback onDoneCallback, mCurrentAdministratorCommissioningAttributes.node_id = nodeId; mCurrentAdministratorCommissioningAttributes.window_status = static_cast(Clusters::AdministratorCommissioning::CommissioningWindowStatusEnum::kWindowNotOpen); - mState = State::Connecting; - mOnDoneCallback = onDoneCallback; - return controller.GetConnectedDevice(nodeId, &mOnDeviceConnectedCallback, &mOnDeviceConnectionFailureCallback); + mOnDoneCallback = onDoneCallback; + MoveToState(State::Connecting); + CHIP_ERROR err = controller.GetConnectedDevice(nodeId, &mOnDeviceConnectedCallback, &mOnDeviceConnectionFailureCallback); + if (err != CHIP_NO_ERROR) + { + MoveToState(State::Idle); + } + return err; } void DeviceSubscription::StopSubscription() diff --git a/examples/fabric-admin/device_manager/DeviceSynchronization.cpp b/examples/fabric-admin/device_manager/DeviceSynchronization.cpp index 0ca2e270826c9e..bc3e9b31fe6b1b 100644 --- a/examples/fabric-admin/device_manager/DeviceSynchronization.cpp +++ b/examples/fabric-admin/device_manager/DeviceSynchronization.cpp @@ -128,35 +128,35 @@ void DeviceSynchronizer::OnAttributeData(const ConcreteDataAttributePath & path, void DeviceSynchronizer::OnReportEnd() { // Report end is at the end of all attributes (success) + MoveToState(State::ReceivedResponse); +} + +void DeviceSynchronizer::OnDone(chip::app::ReadClient * apReadClient) +{ #if defined(PW_RPC_ENABLED) - if (!DeviceMgr().IsCurrentBridgeDevice(mCurrentDeviceData.node_id)) + if (mState == State::ReceivedResponse && !DeviceMgr().IsCurrentBridgeDevice(mCurrentDeviceData.node_id)) { - AddSynchronizedDevice(mCurrentDeviceData); - // TODO(#35077) Figure out how we should reflect CADMIN values of ICD. - if (!mCurrentDeviceData.is_icd) + auto * device = DeviceMgr().FindDeviceByNode(mCurrentDeviceData.node_id); + if (!mCurrentDeviceData.has_unique_id && device) { - VerifyOrDie(mController); - // TODO(#35333) Figure out how we should recover in this circumstance. - CHIP_ERROR err = DeviceSubscriptionManager::Instance().StartSubscription(*mController, mCurrentDeviceData.node_id); - if (err != CHIP_NO_ERROR) + GetUid(device->GetEndpointId()); + if (mState == State::GettingUid) { - ChipLogError(NotSpecified, "Failed start subscription to "); + // GetUid was successful and we rely on callback to call SynchronizationCompleteAddDevice. + return; } } + SynchronizationCompleteAddDevice(); } #else ChipLogError(NotSpecified, "Cannot synchronize device with fabric bridge: RPC not enabled"); #endif -} - -void DeviceSynchronizer::OnDone(chip::app::ReadClient * apReadClient) -{ - // Nothing to do: error reported on OnError or report ended called. - mDeviceSyncInProcess = false; + MoveToState(State::Idle); } void DeviceSynchronizer::OnError(CHIP_ERROR error) { + MoveToState(State::ReceivedError); ChipLogProgress(NotSpecified, "Error fetching device data: %" CHIP_ERROR_FORMAT, error.Format()); } @@ -180,21 +180,22 @@ void DeviceSynchronizer::OnDeviceConnected(chip::Messaging::ExchangeManager & ex if (err != CHIP_NO_ERROR) { ChipLogError(NotSpecified, "Failed to issue read for BasicInformation data"); - mDeviceSyncInProcess = false; + MoveToState(State::Idle); } + MoveToState(State::AwaitingResponse); } void DeviceSynchronizer::OnDeviceConnectionFailure(const chip::ScopedNodeId & peerId, CHIP_ERROR error) { ChipLogError(NotSpecified, "Device Sync failed to connect to " ChipLogFormatX64, ChipLogValueX64(peerId.GetNodeId())); - mDeviceSyncInProcess = false; + MoveToState(State::Idle); } void DeviceSynchronizer::StartDeviceSynchronization(chip::Controller::DeviceController * controller, chip::NodeId nodeId, bool deviceIsIcd) { VerifyOrDie(controller); - if (mDeviceSyncInProcess) + if (mState != State::Idle) { ChipLogError(NotSpecified, "Device Sync NOT POSSIBLE: another sync is in progress"); return; @@ -205,8 +206,85 @@ void DeviceSynchronizer::StartDeviceSynchronization(chip::Controller::DeviceCont mCurrentDeviceData.has_is_icd = true; mCurrentDeviceData.is_icd = deviceIsIcd; - mDeviceSyncInProcess = true; - + ReturnOnFailure(controller->GetConnectedDevice(nodeId, &mOnDeviceConnectedCallback, &mOnDeviceConnectionFailureCallback)); mController = controller; - controller->GetConnectedDevice(nodeId, &mOnDeviceConnectedCallback, &mOnDeviceConnectionFailureCallback); + MoveToState(State::Connecting); +} + +void DeviceSynchronizer::GetUid(EndpointId remoteEndpointIdOfInterest) +{ + VerifyOrDie(mState == State::ReceivedResponse); + VerifyOrDie(mController); + VerifyOrDie(DeviceMgr().IsFabricSyncReady()); + auto remoteBridgeNodeId = DeviceMgr().GetRemoteBridgeNodeId(); + + CHIP_ERROR err = mUidGetter.GetUid( + [this](std::optional aUniqueId) { + if (aUniqueId.has_value()) + { + this->mCurrentDeviceData.has_unique_id = true; + memcpy(this->mCurrentDeviceData.unique_id, aUniqueId.value().data(), aUniqueId.value().size()); + } + else + { + ChipLogError(NotSpecified, "We expected to get UniqueId from remote fabric sync bridge"); + } + this->SynchronizationCompleteAddDevice(); + }, + *mController, remoteBridgeNodeId, remoteEndpointIdOfInterest); + + if (err == CHIP_NO_ERROR) + { + MoveToState(State::GettingUid); + } +} + +void DeviceSynchronizer::SynchronizationCompleteAddDevice() +{ + VerifyOrDie(mState == State::ReceivedResponse || mState == State::GettingUid); + AddSynchronizedDevice(mCurrentDeviceData); + // TODO(#35077) Figure out how we should reflect CADMIN values of ICD. + if (!mCurrentDeviceData.is_icd) + { + VerifyOrDie(mController); + // TODO(#35333) Figure out how we should recover in this circumstance. + CHIP_ERROR err = DeviceSubscriptionManager::Instance().StartSubscription(*mController, mCurrentDeviceData.node_id); + if (err != CHIP_NO_ERROR) + { + ChipLogError(NotSpecified, "Failed start subscription to NodeId:" ChipLogFormatX64, + ChipLogValueX64(mCurrentDeviceData.node_id)); + } + } + MoveToState(State::Idle); +} + +void DeviceSynchronizer::MoveToState(const State targetState) +{ + mState = targetState; + ChipLogDetail(NotSpecified, "DeviceSynchronizer moving to [%10.10s]", GetStateStr()); +} + +const char * DeviceSynchronizer::GetStateStr() const +{ + switch (mState) + { + case State::Idle: + return "Idle"; + + case State::Connecting: + return "Connecting"; + + case State::AwaitingResponse: + return "AwaitingResponse"; + + case State::ReceivedResponse: + return "ReceivedResponse"; + + case State::ReceivedError: + return "ReceivedError"; + + case State::GettingUid: + return "GettingUid"; + } + return "N/A"; } diff --git a/examples/fabric-admin/device_manager/DeviceSynchronization.h b/examples/fabric-admin/device_manager/DeviceSynchronization.h index c5a42378b8be9d..495fecd60bd4b5 100644 --- a/examples/fabric-admin/device_manager/DeviceSynchronization.h +++ b/examples/fabric-admin/device_manager/DeviceSynchronization.h @@ -17,6 +17,8 @@ */ #pragma once +#include "UidGetter.h" + #include #include #include @@ -65,14 +67,31 @@ class DeviceSynchronizer : public chip::app::ReadClient::Callback static DeviceSynchronizer & Instance(); private: + enum class State : uint8_t + { + Idle, ///< Default state that the object starts out in, where no work has commenced + Connecting, ///< We are waiting for OnDeviceConnected or OnDeviceConnectionFailure callbacks to be called + AwaitingResponse, ///< We have started reading BasicInformation cluster attributes + ReceivedResponse, ///< We have received a ReportEnd from reading BasicInformation cluster attributes + ReceivedError, ///< We recieved an error while reading of BasicInformation cluster attributes + GettingUid, ///< We are getting UniqueId from the remote fabric sync bridge. + }; + + void GetUid(chip::EndpointId endpointId); + void SynchronizationCompleteAddDevice(); + + void MoveToState(const State targetState); + const char * GetStateStr() const; + std::unique_ptr mClient; chip::Callback::Callback mOnDeviceConnectedCallback; chip::Callback::Callback mOnDeviceConnectionFailureCallback; + State mState = State::Idle; // mController is expected to remain valid throughout the entire device synchronization process (i.e. when - // mDeviceSyncInProcess is true). + // mState != Idle). chip::Controller::DeviceController * mController = nullptr; - bool mDeviceSyncInProcess = false; chip_rpc_SynchronizedDevice mCurrentDeviceData = chip_rpc_SynchronizedDevice_init_default; + UidGetter mUidGetter; }; diff --git a/examples/fabric-admin/device_manager/UidGetter.cpp b/examples/fabric-admin/device_manager/UidGetter.cpp new file mode 100644 index 00000000000000..baeaba6a02498c --- /dev/null +++ b/examples/fabric-admin/device_manager/UidGetter.cpp @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * 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. + * + */ + +#include "UidGetter.h" + +using namespace ::chip; +using namespace ::chip::app; +using chip::app::ReadClient; + +namespace { + +void OnDeviceConnectedWrapper(void * context, Messaging::ExchangeManager & exchangeMgr, const SessionHandle & sessionHandle) +{ + reinterpret_cast(context)->OnDeviceConnected(exchangeMgr, sessionHandle); +} + +void OnDeviceConnectionFailureWrapper(void * context, const ScopedNodeId & peerId, CHIP_ERROR error) +{ + reinterpret_cast(context)->OnDeviceConnectionFailure(peerId, error); +} + +bool SuccessOrLog(CHIP_ERROR err, const char * name) +{ + if (err == CHIP_NO_ERROR) + { + return true; + } + + ChipLogError(NotSpecified, "Failed to read %s: %" CHIP_ERROR_FORMAT, name, err.Format()); + + return false; +} + +} // namespace + +UidGetter::UidGetter() : + mOnDeviceConnectedCallback(OnDeviceConnectedWrapper, this), + mOnDeviceConnectionFailureCallback(OnDeviceConnectionFailureWrapper, this) +{} + +CHIP_ERROR UidGetter::GetUid(OnDoneCallback onDoneCallback, chip::Controller::DeviceController & controller, chip::NodeId nodeId, + chip::EndpointId endpointId) +{ + assertChipStackLockedByCurrentThread(); + VerifyOrDie(!mCurrentlyGettingUid); + + mEndpointId = endpointId; + mOnDoneCallback = onDoneCallback; + mUniqueIdHasValue = false; + memset(mUniqueId, 0, sizeof(mUniqueId)); + mCurrentlyGettingUid = true; + + CHIP_ERROR err = controller.GetConnectedDevice(nodeId, &mOnDeviceConnectedCallback, &mOnDeviceConnectionFailureCallback); + if (err != CHIP_NO_ERROR) + { + ChipLogError(NotSpecified, "Failed to connect to remote fabric sync bridge %" CHIP_ERROR_FORMAT, err.Format()); + mCurrentlyGettingUid = false; + } + return err; +} + +void UidGetter::OnAttributeData(const ConcreteDataAttributePath & path, TLV::TLVReader * data, const StatusIB & status) +{ + VerifyOrDie(path.mClusterId == Clusters::BridgedDeviceBasicInformation::Id); + + if (!status.IsSuccess()) + { + ChipLogError(NotSpecified, "Response Failure: %" CHIP_ERROR_FORMAT, status.ToChipError().Format()); + return; + } + + switch (path.mAttributeId) + { + case Clusters::BridgedDeviceBasicInformation::Attributes::UniqueID::Id: { + mUniqueIdHasValue = SuccessOrLog(data->GetString(mUniqueId, sizeof(mUniqueId)), "UniqueId"); + break; + } + default: + break; + } +} + +void UidGetter::OnReportEnd() +{ + // We will call mOnDoneCallback in OnDone. +} + +void UidGetter::OnError(CHIP_ERROR error) +{ + ChipLogProgress(NotSpecified, "Error Getting UID: %" CHIP_ERROR_FORMAT, error.Format()); +} + +void UidGetter::OnDone(ReadClient * apReadClient) +{ + mCurrentlyGettingUid = false; + mOnDoneCallback(mUniqueIdHasValue ? std::make_optional(mUniqueId) : std::nullopt); +} + +void UidGetter::OnDeviceConnected(Messaging::ExchangeManager & exchangeMgr, const SessionHandle & sessionHandle) +{ + VerifyOrDie(mCurrentlyGettingUid); + mClient = std::make_unique(app::InteractionModelEngine::GetInstance(), &exchangeMgr, *this /* callback */, + ReadClient::InteractionType::Read); + VerifyOrDie(mClient); + + AttributePathParams readPaths[1]; + readPaths[0] = AttributePathParams(mEndpointId, Clusters::BridgedDeviceBasicInformation::Id, + Clusters::BridgedDeviceBasicInformation::Attributes::UniqueID::Id); + + ReadPrepareParams readParams(sessionHandle); + + readParams.mpAttributePathParamsList = readPaths; + readParams.mAttributePathParamsListSize = 1; + + CHIP_ERROR err = mClient->SendRequest(readParams); + + if (err != CHIP_NO_ERROR) + { + ChipLogError(NotSpecified, "Failed to issue subscription to AdministratorCommissioning data"); + OnDone(nullptr); + return; + } +} + +void UidGetter::OnDeviceConnectionFailure(const ScopedNodeId & peerId, CHIP_ERROR error) +{ + VerifyOrDie(mCurrentlyGettingUid); + ChipLogError(NotSpecified, "DeviceSubscription failed to connect to " ChipLogFormatX64, ChipLogValueX64(peerId.GetNodeId())); + + OnDone(nullptr); +} diff --git a/examples/fabric-admin/device_manager/UidGetter.h b/examples/fabric-admin/device_manager/UidGetter.h new file mode 100644 index 00000000000000..5f99c75198c5dc --- /dev/null +++ b/examples/fabric-admin/device_manager/UidGetter.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * 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. + * + */ + +#pragma once + +#include +#include + +#include +#include + +class UidGetter : public chip::app::ReadClient::Callback +{ +public: + using OnDoneCallback = std::function)>; + + UidGetter(); + + CHIP_ERROR GetUid(OnDoneCallback onDoneCallback, chip::Controller::DeviceController & controller, chip::NodeId nodeId, + chip::EndpointId endpointId); + + /////////////////////////////////////////////////////////////// + // ReadClient::Callback implementation + /////////////////////////////////////////////////////////////// + void OnAttributeData(const chip::app::ConcreteDataAttributePath & path, chip::TLV::TLVReader * data, + const chip::app::StatusIB & status) override; + void OnReportEnd() override; + void OnError(CHIP_ERROR error) override; + void OnDone(chip::app::ReadClient * apReadClient) override; + + /////////////////////////////////////////////////////////////// + // callbacks for CASE session establishment + /////////////////////////////////////////////////////////////// + void OnDeviceConnected(chip::Messaging::ExchangeManager & exchangeMgr, const chip::SessionHandle & sessionHandle); + void OnDeviceConnectionFailure(const chip::ScopedNodeId & peerId, CHIP_ERROR error); + +private: + std::unique_ptr mClient; + + OnDoneCallback mOnDoneCallback; + + chip::Callback::Callback mOnDeviceConnectedCallback; + chip::Callback::Callback mOnDeviceConnectionFailureCallback; + + bool mCurrentlyGettingUid = false; + bool mUniqueIdHasValue = false; + char mUniqueId[33]; + chip::EndpointId mEndpointId; +}; From f95c8e78380bc9d0bdb1d5e9099c8099aef0e4db Mon Sep 17 00:00:00 2001 From: Raul Marquez <130402456+raul-marquez-csa@users.noreply.github.com> Date: Tue, 3 Sep 2024 17:09:44 -0700 Subject: [PATCH 09/15] Fix typo (#35375) --- .../tests/suites/certification/Test_TC_CNET_4_13.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/app/tests/suites/certification/Test_TC_CNET_4_13.yaml b/src/app/tests/suites/certification/Test_TC_CNET_4_13.yaml index d2dbeb418f9f25..8acd24628c910e 100644 --- a/src/app/tests/suites/certification/Test_TC_CNET_4_13.yaml +++ b/src/app/tests/suites/certification/Test_TC_CNET_4_13.yaml @@ -54,7 +54,7 @@ tests: entries as 'NumNetworks'" PICS: CNET.S.A0001 verification: | - ./chip-tool network-commissioning read networks 1 0 + ./chip-tool networkcommissioning read networks 1 0 The test case is not verifiable in RPI platform. As MaxNetworks value is 1 but expected is 4 ( Pre-Condition) @@ -128,7 +128,7 @@ tests: NT_SSID 2. NetworkIn dex is 'MaxNetwo rksValue' 3. Breadcrum b is 2" PICS: CNET.S.C08.Rsp && CNET.S.C05.Tx verification: | - ./chip-tool network-commissioning read networks 1 0 + ./chip-tool networkcommissioning read networks 1 0 The test case is not verifiable in RPI platform. As MaxNetworks value is 1 but expected is 4 ( Pre-Condition) @@ -189,7 +189,7 @@ tests: - label: "Step 13: TH readsNetworksattribute list fromthe DUT" PICS: CNET.S.A0001 verification: | - ./chip-tool network-commissioning read networks 1 0 + ./chip-tool networkcommissioning read networks 1 0 The test case is not verifiable in RPI platform. As MaxNetworks value is 1 but expected is 4 ( Pre-Condition) @@ -208,7 +208,7 @@ tests: - label: "Step 15: TH readsNetworksattribute list fromthe DUT" PICS: CNET.S.A0001 verification: | - ./chip-tool network-commissioning read networks 1 0 + ./chip-tool networkcommissioning read networks 1 0 The test case is not verifiable in RPI platform. As MaxNetworks value is 1 but expected is 4 ( Pre-Condition) @@ -268,7 +268,7 @@ tests: - label: "Step 21: TH readsNetworksattribute list fromthe DUT" PICS: CNET.S.A0001 verification: | - ./chip-tool network-commissioning read networks 1 0 + ./chip-tool networkcommissioning read networks 1 0 The test case is not verifiable in RPI platform. As MaxNetworks value is 1 but expected is 4 ( Pre-Condition) From f8504382b574417db73a602a9bedc47a2a957103 Mon Sep 17 00:00:00 2001 From: Junior Martinez <67972863+jmartinez-silabs@users.noreply.github.com> Date: Tue, 3 Sep 2024 20:25:45 -0400 Subject: [PATCH 10/15] Fix mismatch in gn argument name between its declaration and usage. (#35384) --- third_party/silabs/efr32_sdk.gni | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/third_party/silabs/efr32_sdk.gni b/third_party/silabs/efr32_sdk.gni index ce7b09e3e012ac..c75f67694e8d5c 100644 --- a/third_party/silabs/efr32_sdk.gni +++ b/third_party/silabs/efr32_sdk.gni @@ -61,7 +61,7 @@ declare_args() { sl_active_mode_duration_ms = 1000 # 1s Active Mode Duration sl_active_mode_threshold_ms = 500 # 500ms Active Mode Threshold sl_icd_supported_clients_per_fabric = 2 # 2 registration slots per fabric - sl_use_subscription_synching = false + sl_use_subscription_syncing = false silabs_log_enabled = true @@ -658,7 +658,7 @@ template("efr32_sdk") { ] } - if (sl_use_subscription_synching) { + if (sl_use_subscription_syncing) { defines += [ "CHIP_CONFIG_SYNCHRONOUS_REPORTS_ENABLED=1" ] } From cddc8ca7f51fe756ba047fd46baa72443e75e68c Mon Sep 17 00:00:00 2001 From: yunhanw-google Date: Tue, 3 Sep 2024 17:47:13 -0700 Subject: [PATCH 11/15] [ICD] Set client_type when refreshing token for LIT ICD (#35383) * set client_type when refresh key for ICD * Update RefreshKeySender.cpp --- src/app/icd/client/RefreshKeySender.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/icd/client/RefreshKeySender.cpp b/src/app/icd/client/RefreshKeySender.cpp index 8b6122d0228f95..c49bf79f30a17c 100644 --- a/src/app/icd/client/RefreshKeySender.cpp +++ b/src/app/icd/client/RefreshKeySender.cpp @@ -80,6 +80,7 @@ CHIP_ERROR RefreshKeySender::RegisterClientWithNewKey(Messaging::ExchangeManager registerClientCommand.checkInNodeID = mICDClientInfo.check_in_node.GetNodeId(); registerClientCommand.monitoredSubject = mICDClientInfo.monitored_subject; registerClientCommand.key = mNewKey.Span(); + registerClientCommand.clientType = mICDClientInfo.client_type; return Controller::InvokeCommandRequest(&exchangeMgr, sessionHandle, endpointId, registerClientCommand, onSuccess, onFailure); } From 5d3147c43bf961ad3c40541a9cf08b84f85803d7 Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Tue, 3 Sep 2024 22:01:34 -0400 Subject: [PATCH 12/15] Start a few more helper apps as part of MTRCommissionableBrowserTests. (#35386) Lets us test that we properly see more than one thing that's commissionable. --- .../CHIPTests/MTRCommissionableBrowserTests.m | 33 ++++++++---- .../Framework/CHIPTests/MTRPairingTests.m | 2 - .../CHIPTests/TestHelpers/MTRTestCase.h | 6 +++ .../CHIPTests/TestHelpers/MTRTestCase.mm | 50 ++++++++++++++++--- .../TestHelpers/MTRTestServerAppRunner.h | 6 +++ .../TestHelpers/MTRTestServerAppRunner.m | 20 +++++++- 6 files changed, 97 insertions(+), 20 deletions(-) diff --git a/src/darwin/Framework/CHIPTests/MTRCommissionableBrowserTests.m b/src/darwin/Framework/CHIPTests/MTRCommissionableBrowserTests.m index 6a85bfec431d7f..31ca37ffd4bc94 100644 --- a/src/darwin/Framework/CHIPTests/MTRCommissionableBrowserTests.m +++ b/src/darwin/Framework/CHIPTests/MTRCommissionableBrowserTests.m @@ -17,23 +17,21 @@ #import -// system dependencies -#import - +#import "MTRTestCase.h" #import "MTRTestKeys.h" +#import "MTRTestServerAppRunner.h" #import "MTRTestStorage.h" // Fixture 1: chip-all-clusters-app --KVS "$(mktemp -t chip-test-kvs)" --interface-id -1 -// Fixture 2: chip-all-clusters-app --KVS "$(mktemp -t chip-test-kvs)" --interface-id -1 \ - --dac_provider credentials/development/commissioner_dut/struct_cd_origin_pid_vid_correct/test_case_vector.json \ - --product-id 32768 --discriminator 3839 static const uint16_t kLocalPort = 5541; static const uint16_t kTestVendorId = 0xFFF1u; -static const __auto_type kTestProductIds = @[ @(0x8001u) ]; -static const __auto_type kTestDiscriminators = @[ @(3840u) ]; +static const __auto_type kTestProductIds = @[ @(0x8000u), @(0x8001u) ]; +static const __auto_type kTestDiscriminators = @[ @(2000), @(3839u), @(3840u) ]; static const uint16_t kDiscoverDeviceTimeoutInSeconds = 10; -static const uint16_t kExpectedDiscoveredDevicesCount = 1; +static const uint16_t kExpectedDiscoveredDevicesCount = 3; + +static bool sHelperAppsStarted = false; // Singleton controller we use. static MTRDeviceController * sController = nil; @@ -113,7 +111,7 @@ - (void)controller:(MTRDeviceController *)controller didRemoveCommissionableDevi } @end -@interface MTRCommissionableBrowserTests : XCTestCase +@interface MTRCommissionableBrowserTests : MTRTestCase @end @implementation MTRCommissionableBrowserTests @@ -159,6 +157,21 @@ + (void)tearDown - (void)setUp { [super setUp]; + + if (!sHelperAppsStarted) { + for (NSString * payload in @[ + @"MT:Y.K90SO527JA0648G00", + @"MT:-24J0AFN00I40648G00", + ]) { + __auto_type * appRunner = [[MTRTestServerAppRunner alloc] initCrossTestWithAppName:@"all-clusters" + arguments:@[] + payload:payload + testcase:self]; + XCTAssertNotNil(appRunner); + } + sHelperAppsStarted = true; + } + [self setContinueAfterFailure:NO]; } diff --git a/src/darwin/Framework/CHIPTests/MTRPairingTests.m b/src/darwin/Framework/CHIPTests/MTRPairingTests.m index 125c5a8253fc2e..38f7667590bca7 100644 --- a/src/darwin/Framework/CHIPTests/MTRPairingTests.m +++ b/src/darwin/Framework/CHIPTests/MTRPairingTests.m @@ -190,8 +190,6 @@ - (void)startServerApp arguments:@[ @"--dac_provider", [self absolutePathFor:@"credentials/development/commissioner_dut/struct_cd_origin_pid_vid_correct/test_case_vector.json"], - @"--product-id", - @"32768", ] payload:kOnboardingPayload testcase:self]; diff --git a/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestCase.h b/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestCase.h index c9f20e174d5816..e3668bb90e1fca 100644 --- a/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestCase.h +++ b/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestCase.h @@ -47,6 +47,12 @@ NS_ASSUME_NONNULL_BEGIN * tearDown happens. */ - (void)launchTask:(NSTask *)task; + +/** + * Launch a cross-test task. The task will be automatically terminated when the testsuite + * tearDown happens. + */ +- (void)launchCrossTestTask:(NSTask *)task; #endif // HAVE_NSTASK /** diff --git a/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestCase.mm b/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestCase.mm index e8f317eae9e1c4..fc615cc927d06b 100644 --- a/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestCase.mm +++ b/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestCase.mm @@ -19,12 +19,42 @@ #import "MTRTestCase.h" +#if HAVE_NSTASK +// Tasks that are not scoped to a specific test, but rather to a specific test suite. +static NSMutableSet * runningCrossTestTasks; + +static void ClearTaskSet(NSMutableSet * __strong & tasks) +{ + for (NSTask * task in tasks) { + NSLog(@"Terminating task %@", task); + [task terminate]; + } + tasks = nil; +} +#endif // HAVE_NSTASK + @implementation MTRTestCase { #if HAVE_NSTASK NSMutableSet * _runningTasks; #endif // NSTask } ++ (void)setUp +{ + [super setUp]; + +#if HAVE_NSTASK + runningCrossTestTasks = [[NSMutableSet alloc] init]; +#endif // HAVE_NSTASK +} + ++ (void)tearDown +{ +#if HAVE_NSTASK + ClearTaskSet(runningCrossTestTasks); +#endif // HAVE_NSTASK +} + - (void)setUp { #if HAVE_NSTASK @@ -48,11 +78,7 @@ - (void)tearDown #endif #if HAVE_NSTASK - for (NSTask * task in _runningTasks) { - NSLog(@"Terminating task %@", task); - [task terminate]; - } - _runningTasks = nil; + ClearTaskSet(_runningTasks); #endif // HAVE_NSTASK [super tearDown]; @@ -76,14 +102,26 @@ - (void)runTask:(NSTask *)task XCTAssertEqual([task terminationStatus], 0); } -- (void)launchTask:(NSTask *)task +- (void)doLaunchTask:(NSTask *)task { NSError * launchError; [task launchAndReturnError:&launchError]; XCTAssertNil(launchError); +} + +- (void)launchTask:(NSTask *)task +{ + [self doLaunchTask:task]; [_runningTasks addObject:task]; } + +- (void)launchCrossTestTask:(NSTask *)task +{ + [self doLaunchTask:task]; + + [runningCrossTestTasks addObject:task]; +} #endif // HAVE_NSTASK - (NSString *)absolutePathFor:(NSString *)matterRootRelativePath diff --git a/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestServerAppRunner.h b/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestServerAppRunner.h index 9297b76c6fcb66..b54b7dbd78cfce 100644 --- a/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestServerAppRunner.h +++ b/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestServerAppRunner.h @@ -45,6 +45,12 @@ NS_ASSUME_NONNULL_BEGIN */ - (instancetype)initWithAppName:(NSString *)name arguments:(NSArray *)arguments payload:(NSString *)payload testcase:(MTRTestCase *)testcase; +/** + * Same thing, but initialize as a "cross test" helper, which is not killed at + * the end of the current test (but is killed at the end of the current suite). + */ +- (instancetype)initCrossTestWithAppName:(NSString *)name arguments:(NSArray *)arguments payload:(NSString *)payload testcase:(MTRTestCase *)testcase; + /** * Get the unique index that will be used for the next initialization. This * allows including that index in the arguments provided. diff --git a/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestServerAppRunner.m b/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestServerAppRunner.m index 9bc9e8e168dcda..cc64d62e71d516 100644 --- a/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestServerAppRunner.m +++ b/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestServerAppRunner.m @@ -36,7 +36,7 @@ @implementation MTRTestServerAppRunner { #endif } -- (instancetype)initWithAppName:(NSString *)name arguments:(NSArray *)arguments payload:(NSString *)payload testcase:(MTRTestCase *)testcase +- (instancetype)initInternalWithAppName:(NSString *)name arguments:(NSArray *)arguments payload:(NSString *)payload testcase:(MTRTestCase *)testcase isCrossTest:(BOOL)isCrossTest { #if !HAVE_NSTASK XCTFail("Unable to start server app when we do not have NSTask"); @@ -75,6 +75,8 @@ - (instancetype)initWithAppName:(NSString *)name arguments:(NSArray [NSString stringWithFormat:@"%llu", passcode.unsignedLongLongValue], @"--KVS", [NSString stringWithFormat:@"/tmp/chip-%@-kvs%u", name, _uniqueIndex], + @"--product-id", + [NSString stringWithFormat:@"%u", parsedPayload.productID.unsignedShortValue], ]; __auto_type * allArguments = [forcedArguments arrayByAddingObjectsFromArray:arguments]; @@ -90,7 +92,11 @@ - (instancetype)initWithAppName:(NSString *)name arguments:(NSArray _appTask.standardOutput = [NSFileHandle fileHandleForWritingAtPath:outFile]; _appTask.standardError = [NSFileHandle fileHandleForWritingAtPath:errorFile]; - [testcase launchTask:_appTask]; + if (isCrossTest) { + [testcase launchCrossTestTask:_appTask]; + } else { + [testcase launchTask:_appTask]; + } NSLog(@"Started chip-%@-app (%@) with arguments %@ stdout=%@ and stderr=%@", name, _appTask, allArguments, outFile, errorFile); @@ -98,6 +104,16 @@ - (instancetype)initWithAppName:(NSString *)name arguments:(NSArray #endif // HAVE_NSTASK } +- (instancetype)initWithAppName:(NSString *)name arguments:(NSArray *)arguments payload:(NSString *)payload testcase:(MTRTestCase *)testcase +{ + return [self initInternalWithAppName:name arguments:arguments payload:payload testcase:testcase isCrossTest:NO]; +} + +- (instancetype)initCrossTestWithAppName:(NSString *)name arguments:(NSArray *)arguments payload:(NSString *)payload testcase:(MTRTestCase *)testcase +{ + return [self initInternalWithAppName:name arguments:arguments payload:payload testcase:testcase isCrossTest:YES]; +} + + (unsigned)nextUniqueIndex { return sAppRunnerIndex; From 7ad6aa8baa4105a28a44dbc7a15fc9b35675ea8b Mon Sep 17 00:00:00 2001 From: Shubham Patil Date: Wed, 4 Sep 2024 08:01:31 +0530 Subject: [PATCH 13/15] [ESP32] Option to configure the pool allocation from heap (#30764) * [ESP32] Option to configure the system buffer pool allocation from heap * address reviews * minor adjustment to help text * some minor adjustments * Add notes regarding expected failures --- config/esp32/components/chip/Kconfig | 20 ++++++++++++++++++++ src/platform/ESP32/CHIPPlatformConfig.h | 6 ++++++ 2 files changed, 26 insertions(+) diff --git a/config/esp32/components/chip/Kconfig b/config/esp32/components/chip/Kconfig index d3f621f98f4024..147bb97af25278 100644 --- a/config/esp32/components/chip/Kconfig +++ b/config/esp32/components/chip/Kconfig @@ -163,6 +163,26 @@ menu "CHIP Core" help Option to enable/disable CHIP data model. + config CHIP_SYSTEM_CONFIG_POOL_USE_HEAP + bool "Use heap memory to allocate object pools" + default n + help + This option enables the use of heap memory to allocate object pools. + When enabled, object pools are not pre-allocated. + Additionally, the maximum number of entries that can be allocated is + only limited by the available heap memory. + + This option can be useful if you encounter static DRAM overflow. + + NOTE: Since there is no cap on pool sizes, this may lead to issues + where embedded code assumes the pool size is limited, and no other + mechanisms are in place to restrict the size of allocations. + + NOTE: If enabled and the free heap is exhausted, this may result in + undefined behavior, potential non-compliance with specifications, + or failure during certification tests. Even if it passes, it may fail + to function properly with actual controllers. + endmenu # "General Options" menu "Networking Options" diff --git a/src/platform/ESP32/CHIPPlatformConfig.h b/src/platform/ESP32/CHIPPlatformConfig.h index 82b6a6262a1e1b..24fbf7c4f1b809 100644 --- a/src/platform/ESP32/CHIPPlatformConfig.h +++ b/src/platform/ESP32/CHIPPlatformConfig.h @@ -146,3 +146,9 @@ #ifndef CHIP_CONFIG_RMP_DEFAULT_MAX_RETRANS #define CHIP_CONFIG_RMP_DEFAULT_MAX_RETRANS CONFIG_MRP_MAX_RETRANS #endif // CHIP_CONFIG_RMP_DEFAULT_MAX_RETRANS + +#ifdef CONFIG_CHIP_SYSTEM_CONFIG_POOL_USE_HEAP +#define CHIP_SYSTEM_CONFIG_POOL_USE_HEAP 1 +#else +#define CHIP_SYSTEM_CONFIG_POOL_USE_HEAP 0 +#endif From 30cff8e6e1be3be65bd3901c09f6089d4159c40a Mon Sep 17 00:00:00 2001 From: Junior Martinez <67972863+jmartinez-silabs@users.noreply.github.com> Date: Tue, 3 Sep 2024 23:28:13 -0400 Subject: [PATCH 14/15] Use persistent and subscription resumption implementation of ShouldCheckInMsgsBeSentAtActiveModeFunction by setting chip_subscription_timeout_resumption=true for Linux reference lit-icd-app (#35376) --- examples/lit-icd-app/linux/args.gni | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/lit-icd-app/linux/args.gni b/examples/lit-icd-app/linux/args.gni index 09ea58ef2a51d5..b1567dd6284893 100644 --- a/examples/lit-icd-app/linux/args.gni +++ b/examples/lit-icd-app/linux/args.gni @@ -28,6 +28,6 @@ matter_enable_tracing_support = true # ICD configurations chip_enable_icd_server = true -chip_subscription_timeout_resumption = false +chip_subscription_timeout_resumption = true chip_icd_report_on_active_mode = true chip_enable_icd_lit = true From 1e1fe1e62c499e348c9eda90107c9bd6e61b24f9 Mon Sep 17 00:00:00 2001 From: Amine Alami <43780877+Alami-Amine@users.noreply.github.com> Date: Wed, 4 Sep 2024 10:10:19 +0200 Subject: [PATCH 15/15] Integration of pw_fuzzer using FuzzTest (#34274) * latest trial to build pw_fuzz * migrating FuzzPayloadDecoder FuzzTest * fix error related to latomic * adding template for pw_fuzz_tests * fix for linux_sysroot issue * adding FuzzTests * fixing warning issue * adding support to build pw-fuzztests with build_examples.py * Restyled by whitespace * Restyled by clang-format * adding pw_fuzz_tests to default target * fixing build_examples test golden standard * Adding Fuzzing Targets * Adding Documentation * cleaning-up tests * spelling mistakes * integrating comments --------- Co-authored-by: Restyled.io --- .github/.wordlist.txt | 3 + .gitmodules | 16 ++ BUILD.gn | 16 ++ build/chip/fuzz_test.gni | 59 +++++++ build/toolchain/pw_fuzzer/BUILD.gn | 70 ++++++++ docs/guides/BUILDING.md | 20 +++ docs/testing/fuzz_testing.md | 157 ++++++++++++++++++ scripts/build/build/targets.py | 4 + scripts/build/builders/host.py | 6 + .../build/testdata/all_targets_linux_x64.txt | 4 +- src/credentials/tests/BUILD.gn | 10 ++ src/credentials/tests/FuzzChipCertPW.cpp | 95 +++++++++++ src/lib/core/tests/BUILD.gn | 10 ++ src/lib/core/tests/FuzzTlvReaderPW.cpp | 35 ++++ src/lib/dnssd/minimal_mdns/tests/BUILD.gn | 10 ++ .../tests/FuzzPacketParsingPW.cpp | 132 +++++++++++++++ src/lib/format/tests/BUILD.gn | 15 ++ src/lib/format/tests/FuzzPayloadDecoderPW.cpp | 73 ++++++++ src/setup_payload/tests/BUILD.gn | 10 ++ src/setup_payload/tests/FuzzBase38PW.cpp | 77 +++++++++ third_party/abseil-cpp/src | 1 + third_party/fuzztest | 1 + third_party/googletest | 1 + third_party/re2/src | 1 + 24 files changed, 824 insertions(+), 2 deletions(-) create mode 100644 build/toolchain/pw_fuzzer/BUILD.gn create mode 100644 docs/testing/fuzz_testing.md create mode 100644 src/credentials/tests/FuzzChipCertPW.cpp create mode 100644 src/lib/core/tests/FuzzTlvReaderPW.cpp create mode 100644 src/lib/dnssd/minimal_mdns/tests/FuzzPacketParsingPW.cpp create mode 100644 src/lib/format/tests/FuzzPayloadDecoderPW.cpp create mode 100644 src/setup_payload/tests/FuzzBase38PW.cpp create mode 160000 third_party/abseil-cpp/src create mode 160000 third_party/fuzztest create mode 160000 third_party/googletest create mode 160000 third_party/re2/src diff --git a/.github/.wordlist.txt b/.github/.wordlist.txt index 153345827ee086..4444d8299d26cb 100644 --- a/.github/.wordlist.txt +++ b/.github/.wordlist.txt @@ -37,6 +37,7 @@ AdvSendAdvert AE aef AES +AFL AIDL algs alloc @@ -570,6 +571,7 @@ fsync ftd fullclean fuzzer +fuzztest FW gbl gcloud @@ -1008,6 +1010,7 @@ optionOverride optionsMask optionsOverride orgs +OSS OTA OTADownloader otaDownloadPath diff --git a/.gitmodules b/.gitmodules index 78a6cabb940d05..40801ec9742517 100644 --- a/.gitmodules +++ b/.gitmodules @@ -329,3 +329,19 @@ path = third_party/infineon/psoc6/psoc6_sdk/libs/lwip-network-interface-integration url = https://github.com/Infineon/lwip-network-interface-integration.git platforms = infineon +[submodule "third_party/abseil-cpp/src"] + path = third_party/abseil-cpp/src + url = https://github.com/abseil/abseil-cpp.git + platforms = linux,darwin +[submodule "third_party/fuzztest"] + path = third_party/fuzztest + url = https://github.com/google/fuzztest.git + platforms = linux,darwin +[submodule "third_party/googletest"] + path = third_party/googletest + url = https://github.com/google/googletest + platforms = linux,darwin +[submodule "third_party/re2/src"] + path = third_party/re2/src + url = https://github.com/google/re2.git + platforms = linux,darwin diff --git a/BUILD.gn b/BUILD.gn index c8e41976599173..4efa25007aa523 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -62,6 +62,18 @@ if (current_toolchain != "${dir_pw_toolchain}/default:default") { } } + if (pw_enable_fuzz_test_targets) { + group("pw_fuzz_tests") { + deps = [ + "${chip_root}/src/credentials/tests:fuzz-chip-cert-pw(//build/toolchain/pw_fuzzer:chip_pw_fuzztest)", + "${chip_root}/src/lib/core/tests:fuzz-tlv-reader-pw(//build/toolchain/pw_fuzzer:chip_pw_fuzztest)", + "${chip_root}/src/lib/dnssd/minimal_mdns/tests:fuzz-minmdns-packet-parsing-pw(//build/toolchain/pw_fuzzer:chip_pw_fuzztest)", + "${chip_root}/src/lib/format/tests:fuzz-payload-decoder-pw(//build/toolchain/pw_fuzzer:chip_pw_fuzztest)", + "${chip_root}/src/setup_payload/tests:fuzz-setup-payload-base38-pw(//build/toolchain/pw_fuzzer:chip_pw_fuzztest)", + ] + } + } + # Matter's in-tree pw_python_package or pw_python_distribution targets. _matter_python_packages = [ "//examples/chef", @@ -140,6 +152,10 @@ if (current_toolchain != "${dir_pw_toolchain}/default:default") { deps += [ "//:fuzz_tests" ] } + if (pw_enable_fuzz_test_targets) { + deps += [ "//:pw_fuzz_tests" ] + } + if (chip_device_platform != "none") { deps += [ "${chip_root}/src/app/server" ] } diff --git a/build/chip/fuzz_test.gni b/build/chip/fuzz_test.gni index 784ed60273b02a..98def1dab0463d 100644 --- a/build/chip/fuzz_test.gni +++ b/build/chip/fuzz_test.gni @@ -14,12 +14,17 @@ import("//build_overrides/build.gni") import("//build_overrides/chip.gni") +import("//build_overrides/pigweed.gni") + import("${build_root}/config/compiler/compiler.gni") import("${chip_root}/build/chip/tests.gni") +import("${dir_pw_unit_test}/test.gni") declare_args() { enable_fuzz_test_targets = is_clang && chip_build_tests && (current_os == "linux" || current_os == "mac") + + pw_enable_fuzz_test_targets = false } # Define a fuzz target for chip. @@ -66,3 +71,57 @@ template("chip_fuzz_target") { } } } + +# Define a fuzz target for Matter using pw_fuzzer and Google FuzzTest Framework. +# +# Google FuzzTest is only supported on Linux and MacOS using Clang: +# +# Sample usage +# +# chip_pw_fuzz_target("fuzz-target-name") { +# test_source = [ +# "FuzzTarget.cpp", # Fuzz target +# ] +# +# public_deps = [ +# "${chip_root}/src/lib/foo", # add dependencies here +# ] +# } +# +# +template("chip_pw_fuzz_target") { + if (defined(invoker.test_source)) { + _test_output_dir = "${root_out_dir}/tests" + + if (defined(invoker.output_dir)) { + _test_output_dir = invoker.output_dir + } + + pw_test(target_name) { + forward_variables_from(invoker, + [ + "deps", + "public_deps", + "cflags", + "configs", + "remove_configs", + ]) + + # TODO: remove this after pw_fuzzer's integration with OSS-Fuzz is complete. + #just a test for running FuzzTest with libfuzzer-compatibility mode, since this is the mode supported by OSS-fuzz + # defines = [ + # "FUZZTEST_COMPATIBILITY_MODE=libfuzzer", + # "MAKE_BUILD_TYPE=RelWithDebug", + # ] + + sources = invoker.test_source + output_dir = _test_output_dir + + deps = [ "$dir_pw_fuzzer:fuzztest" ] + + # this is necessary so FuzzTest is compiled into an executable in third_party/pigweed/repo/pw_unit_test/test.gni + # otherwise it will be built successfully but with FuzzTarget.DISABLED.ninja and no executable. + enable_if = true + } + } +} diff --git a/build/toolchain/pw_fuzzer/BUILD.gn b/build/toolchain/pw_fuzzer/BUILD.gn new file mode 100644 index 00000000000000..385e57cc81be39 --- /dev/null +++ b/build/toolchain/pw_fuzzer/BUILD.gn @@ -0,0 +1,70 @@ +# 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. + +import("//build_overrides/build.gni") +import("//build_overrides/pigweed.gni") + +import("$dir_pigweed/targets/host/target_toolchains.gni") +import("${build_root}/toolchain/gcc_toolchain.gni") + +# creating a secondary toolchain to be used with pw_fuzzer FuzzTests +# This toolchain is downstreamed from pigweed's pw_target_toolchain_host.clang_fuzz +# it allows us to specifically use googletest for fuzzing (instead of the lighter version of googletest used for unit testing) + +gcc_toolchain("chip_pw_fuzztest") { + forward_variables_from(pw_target_toolchain_host.clang_fuzz, "*", [ "name" ]) + + toolchain_args = { + # This is needed to have the defaults passed from pw_target_toolchain_host.clang_fuzz to the current scope + forward_variables_from(defaults, "*") + + pw_unit_test_MAIN = "$dir_pw_fuzzer:fuzztest_main" + pw_unit_test_BACKEND = "$dir_pw_fuzzer:gtest" + + # The next three lines are needed by the gcc_toolchain template + current_os = host_os + current_cpu = host_cpu + is_clang = true + + # the upstream pigweed host_clang toolchain defines a default sysroot, which results in build errors + # since it does not include SSL lib and is supposed to be minimal by design. + # by removing this default config, we will use the system's libs. Otherwise we can define our own sysroot. + # discussion on: https://discord.com/channels/691686718377558037/1275092695764959232 + remove_default_configs = [ "$dir_pw_toolchain/host_clang:linux_sysroot" ] + + # when is_debug = true, we pass -O0 to cflags and ldflags, while upstream pw_fuzzer toolchain defines "optimize_speed" config that passes -O2. + # This condition was added to prevent mixing the flags + if (is_debug) { + remove_default_configs += [ "$dir_pw_build:optimize_speed" ] + } + + # removing pigweed downstreamed configs related to warnings + # These are triggering an error related to -Wcast-qual in third_party/nlio + remove_default_configs += [ + "$dir_pw_build:strict_warnings", + "$dir_pw_build:extra_strict_warnings", + ] + + # the third_party abseil-cpp triggers warnings related to [-Wformat-nonliteral] + treat_warnings_as_errors = false + + dir_pw_third_party_abseil_cpp = "//third_party/abseil-cpp/src" + dir_pw_third_party_fuzztest = "//third_party/fuzztest" + dir_pw_third_party_googletest = "//third_party/googletest" + + # TODO: Seems that re2 support within FuzzTest was deprecated, keeping it defined is triggering warning + # Remove if re2 is indeed not needed + # dir_pw_third_party_re2 = "//third_party/re2/src" + } +} diff --git a/docs/guides/BUILDING.md b/docs/guides/BUILDING.md index 7cd660227ed165..941b5328887e31 100644 --- a/docs/guides/BUILDING.md +++ b/docs/guides/BUILDING.md @@ -382,6 +382,26 @@ They pick up environment variables such as `$CFLAGS`, `$CXXFLAGS` and You likely want `libfuzzer` + `asan` builds instead for local testing. +### `pw_fuzzer` `FuzzTests` + +An Alternative way for writing and running Fuzz Tests is Google's `FuzzTest` +framework, integrated through `pw_fuzzer`. The Tests will have to be built and +executed manually. + +``` +./scripts/build/build_examples.py --target linux-x64-tests-clang-pw-fuzztest build +``` + +NOTE: `asan` is enabled by default in FuzzTest, so please do not add it in +build_examples.py invocation. + +Tests will be located in: +`out/linux-x64-tests-clang-pw-fuzztest/chip_pw_fuzztest/tests/` where +`chip_pw_fuzztest` is the name of the toolchain used. + +- Details on How To Run Fuzz Tests in + [Running FuzzTests](https://github.com/project-chip/connectedhomeip/blob/master/docs/testing/fuzz_testing.md) + ## Build custom configuration The build is configured by setting build arguments. These you can set in one of diff --git a/docs/testing/fuzz_testing.md b/docs/testing/fuzz_testing.md new file mode 100644 index 00000000000000..b7faeae463a10e --- /dev/null +++ b/docs/testing/fuzz_testing.md @@ -0,0 +1,157 @@ +# Fuzz testing + +- Fuzz Testing involves providing random and unexpected data as input to + functions and methods to uncover bugs, security vulnerabilities, or to + determine if the software crashes. +- it is often continuous; the function under test is called in iteration with + thousands of different inputs. +- Fuzz testing is often done with sanitizers enabled; to catch memory errors + and undefined behavior. +- The most commonly used fuzz testing frameworks for C/C++ are LibFuzzer and + AFL. +- [Google's FuzzTest](https://github.com/google/fuzztest) is a newer framework + that simplifies writing fuzz tests with user-friendly APIs and offers more + control over input generation. It also integrates seamlessly with Google + Test (GTest). + +## `Google's FuzzTest` + +- Google FuzzTest is integrated through Pigweed + [pw_fuzzer](https://pigweed.dev/pw_fuzzer/concepts.html). + +### Use cases + +1. Finding Undefined Behavior with Sanitizers: + + - Running fuzz tests while checking if a crash or other sanitizer-detected + error occurs, allowing detection of subtle memory issues like buffer + overflows and use-after-free errors. + +2. Find Correctness Bugs using Assertions: + - For example, in Round trip Fuzzing, fuzzed input is encoded, decoded, and + then verified to match the original input. An example of this can be found + in src/setup_payload/tests/FuzzBase38PW.cpp. + +- More information can be found in the + [FuzzTest Use Cases](https://github.com/google/fuzztest/blob/main/doc/use-cases.md) + documentation. + +### Writing FuzzTests + +Keywords: Property Function, Input Domain + +- FuzzTests are instantiated through the macro call of `FUZZ_TEST`: + +```cpp +FUZZ_TEST(TLVReader, FuzzTlvReader).WithDomains(fuzztest::Arbitrary>()); +``` + +- The Macro invocation calls the **Property Function**, which is + `FuzzTlvReader` above. + +- The **input domains** define the range and type of inputs that the + **property function** will receive during fuzzing, specified using the + `.WithDomains()` clause. +- In the macro above, FuzzTest will generate a wide range of possible byte + vectors to thoroughly test the `FuzzTlvReader` function. + +#### The Property Function + +```cpp +// The Property Function +void FuzzTlvRead(const std::vector & bytes) +{ + TLVReader reader; + reader.Init(bytes.data(), bytes.size()); + chip::TLV::Utilities::Iterate(reader, FuzzIterator, nullptr); +} +``` + +- The Property Functions must return a `void` +- The function will be run with many different inputs in the same process, + trying to trigger a crash. +- It is possible to include Assertions such as during Round-Trip Fuzzing + +- More Information: + https://github.com/google/fuzztest/blob/main/doc/fuzz-test-macro.md#the-property-function + +#### Input Domains + +- FuzzTest Offers many Input Domains, all of which are part of the + `fuzztest::` namespace. +- All native C++ types can be used through `Arbitrary()`: + +```cpp +FUZZ_TEST(Base38Decoder, FuzzQRCodeSetupPayloadParser).WithDomains(Arbitrary()); +``` + +- using vector domains is one of the most common. It is possible to limit the + size of the input vectors (or any container domain) using `.WithMaxSize()` + or `.WithMinSize()`, as shown below: + +```cpp +FUZZ_TEST(MinimalmDNS, TxtResponderFuzz).WithDomains(Arbitrary>().WithMaxSize(254)); +``` + +- `ElementOf` is particularly useful as it allows us to define a domain by + explicitly enumerating the set of values in it and passing it to FuzzTest + invocation. Example: + +```cpp +auto AnyProtocolID() +{ + return ElementOf({ chip::Protocols::SecureChannel::Id, chip::Protocols::InteractionModel::Id, chip::Protocols::BDX::Id, + chip::Protocols::UserDirectedCommissioning::Id }); +} + +FUZZ_TEST(PayloadDecoder, RunDecodeFuzz).WithDomains(Arbitrary>(), AnyProtocolID(), Arbitrary()); +``` + +- A detailed reference for input domains can be found here: + [FuzzTest Domain Reference](https://github.com/google/fuzztest/blob/main/doc/domains-reference.md#elementof-domains-element-of). + +### Running FuzzTests + +There are several ways to run the tests: + +1. Unit-test mode (where the inputs are only fuzzed for a second): + +```bash +./fuzz-chip-cert-pw +``` + +2. Continuous fuzzing mode; we need to first list the tests, then specify the + FuzzTestCase to run: + +```bash +$ ./fuzz-chip-cert-pw --list_fuzz_tests +[.] Sanitizer coverage enabled. Counter map size: 11134, Cmp map size: 262144 +[*] Fuzz test: ChipCert.ChipCertFuzzer +[*] Fuzz test: ChipCert.DecodeChipCertFuzzer + +$ ./fuzz-chip-cert-pw --fuzz=ChipCert.DecodeChipCertFuzzer +``` + +3. Running all Tests in a TestSuite for a specific time, e.g for 10 minutes + +```bash +#both Fuzz Tests will be run for 10 minutes each +./fuzz-chip-cert-pw --fuzz_for=10m +``` + +4. For Help + +```bash +# FuzzTest related help +./fuzz-chip-cert-pw --helpfull + +# gtest related help +./fuzz-chip-cert-pw --help + +``` + +#### TO ADD: + +- More Information on Test Fixtures (After issues are resolved) +- How to add FuzzTests to the Build System +- More Information on OSS-FUZZ diff --git a/scripts/build/build/targets.py b/scripts/build/build/targets.py index efb66120ebd97b..378a286f5bf5a4 100755 --- a/scripts/build/build/targets.py +++ b/scripts/build/build/targets.py @@ -77,6 +77,8 @@ def BuildHostFakeTarget(): "-clang").ExceptIfRe('-ossfuzz') target.AppendModifier("ossfuzz", fuzzing_type=HostFuzzingType.OSS_FUZZ).OnlyIfRe( "-clang").ExceptIfRe('-libfuzzer') + target.AppendModifier("pw-fuzztest", fuzzing_type=HostFuzzingType.PW_FUZZTEST).OnlyIfRe( + "-clang").ExceptIfRe('-(libfuzzer|ossfuzz|asan)') target.AppendModifier('coverage', use_coverage=True).OnlyIfRe( '-(chip-tool|all-clusters)') target.AppendModifier('dmalloc', use_dmalloc=True) @@ -178,6 +180,8 @@ def BuildHostTarget(): "-clang").ExceptIfRe('-ossfuzz') target.AppendModifier("ossfuzz", fuzzing_type=HostFuzzingType.OSS_FUZZ).OnlyIfRe( "-clang").ExceptIfRe('-libfuzzer') + target.AppendModifier("pw-fuzztest", fuzzing_type=HostFuzzingType.PW_FUZZTEST).OnlyIfRe( + "-clang").ExceptIfRe('-(libfuzzer|ossfuzz|asan)') target.AppendModifier('coverage', use_coverage=True).OnlyIfRe( '-(chip-tool|all-clusters|tests)') target.AppendModifier('dmalloc', use_dmalloc=True) diff --git a/scripts/build/builders/host.py b/scripts/build/builders/host.py index c51a8ee88cb0ee..b69421a782e948 100644 --- a/scripts/build/builders/host.py +++ b/scripts/build/builders/host.py @@ -42,6 +42,7 @@ class HostFuzzingType(Enum): NONE = auto() LIB_FUZZER = auto() OSS_FUZZ = auto() + PW_FUZZTEST = auto() class HostApp(Enum): @@ -379,6 +380,8 @@ def __init__(self, root, runner, app: HostApp, board=HostBoard.NATIVE, self.extra_gn_options.append('is_libfuzzer=true') elif fuzzing_type == HostFuzzingType.OSS_FUZZ: self.extra_gn_options.append('oss_fuzz=true') + elif fuzzing_type == HostFuzzingType.PW_FUZZTEST: + self.extra_gn_options.append('pw_enable_fuzz_test_targets=true') if imgui_ui: self.extra_gn_options.append('chip_examples_enable_imgui_ui=true') @@ -468,6 +471,9 @@ def __init__(self, root, runner, app: HostApp, board=HostBoard.NATIVE, if self.app == HostApp.TESTS and fuzzing_type != HostFuzzingType.NONE: self.build_command = 'fuzz_tests' + if self.app == HostApp.TESTS and fuzzing_type == HostFuzzingType.PW_FUZZTEST: + self.build_command = 'pw_fuzz_tests' + def GnBuildArgs(self): if self.board == HostBoard.NATIVE: return self.extra_gn_options diff --git a/scripts/build/testdata/all_targets_linux_x64.txt b/scripts/build/testdata/all_targets_linux_x64.txt index 3c5b29050621fe..07b409c75b3b53 100644 --- a/scripts/build/testdata/all_targets_linux_x64.txt +++ b/scripts/build/testdata/all_targets_linux_x64.txt @@ -8,8 +8,8 @@ cyw30739-{cyw30739b2_p5_evk_01,cyw30739b2_p5_evk_02,cyw30739b2_p5_evk_03,cyw9307 efr32-{brd2704b,brd4316a,brd4317a,brd4318a,brd4319a,brd4186a,brd4187a,brd2601b,brd4187c,brd4186c,brd2703a,brd4338a}-{window-covering,switch,unit-test,light,lock,thermostat,pump}[-rpc][-with-ota-requestor][-icd][-low-power][-shell][-no-logging][-openthread-mtd][-heap-monitoring][-no-openthread-cli][-show-qr-code][-wifi][-rs9116][-wf200][-siwx917][-ipv4][-additional-data-advertising][-use-ot-lib][-use-ot-coap-lib][-no-version][-skip-rps-generation] esp32-{m5stack,c3devkit,devkitc,qemu}-{all-clusters,all-clusters-minimal,energy-management,ota-provider,ota-requestor,shell,light,lock,bridge,temperature-measurement,ota-requestor,tests}[-rpc][-ipv6only][-tracing] genio-lighting-app -linux-fake-tests[-mbedtls][-boringssl][-asan][-tsan][-ubsan][-libfuzzer][-ossfuzz][-coverage][-dmalloc][-clang] -linux-{x64,arm64}-{rpc-console,all-clusters,all-clusters-minimal,chip-tool,thermostat,java-matter-controller,kotlin-matter-controller,minmdns,light,light-data-model-no-unique-id,lock,shell,ota-provider,ota-requestor,simulated-app1,simulated-app2,python-bindings,tv-app,tv-casting-app,bridge,fabric-admin,fabric-bridge,tests,chip-cert,address-resolve-tool,contact-sensor,dishwasher,microwave-oven,refrigerator,rvc,air-purifier,lit-icd,air-quality-sensor,network-manager,energy-management}[-nodeps][-nlfaultinject][-platform-mdns][-minmdns-verbose][-libnl][-same-event-loop][-no-interactive][-ipv6only][-no-ble][-no-wifi][-no-thread][-mbedtls][-boringssl][-asan][-tsan][-ubsan][-libfuzzer][-ossfuzz][-coverage][-dmalloc][-clang][-test][-rpc][-with-ui][-evse-test-event][-enable-dnssd-tests][-disable-dnssd-tests][-chip-casting-simplified][-data-model-check][-data-model-disabled][-data-model-enabled] +linux-fake-tests[-mbedtls][-boringssl][-asan][-tsan][-ubsan][-libfuzzer][-ossfuzz][-pw-fuzztest][-coverage][-dmalloc][-clang] +linux-{x64,arm64}-{rpc-console,all-clusters,all-clusters-minimal,chip-tool,thermostat,java-matter-controller,kotlin-matter-controller,minmdns,light,light-data-model-no-unique-id,lock,shell,ota-provider,ota-requestor,simulated-app1,simulated-app2,python-bindings,tv-app,tv-casting-app,bridge,fabric-admin,fabric-bridge,tests,chip-cert,address-resolve-tool,contact-sensor,dishwasher,microwave-oven,refrigerator,rvc,air-purifier,lit-icd,air-quality-sensor,network-manager,energy-management}[-nodeps][-nlfaultinject][-platform-mdns][-minmdns-verbose][-libnl][-same-event-loop][-no-interactive][-ipv6only][-no-ble][-no-wifi][-no-thread][-mbedtls][-boringssl][-asan][-tsan][-ubsan][-libfuzzer][-ossfuzz][-pw-fuzztest][-coverage][-dmalloc][-clang][-test][-rpc][-with-ui][-evse-test-event][-enable-dnssd-tests][-disable-dnssd-tests][-chip-casting-simplified][-data-model-check][-data-model-disabled][-data-model-enabled] linux-x64-efr32-test-runner[-clang] imx-{chip-tool,lighting-app,thermostat,all-clusters-app,all-clusters-minimal-app,ota-provider-app}[-release] infineon-psoc6-{lock,light,all-clusters,all-clusters-minimal}[-ota][-updateimage][-trustm] diff --git a/src/credentials/tests/BUILD.gn b/src/credentials/tests/BUILD.gn index 393b246ef20ee3..46e1f724349102 100644 --- a/src/credentials/tests/BUILD.gn +++ b/src/credentials/tests/BUILD.gn @@ -84,3 +84,13 @@ if (enable_fuzz_test_targets) { ] } } + +if (pw_enable_fuzz_test_targets) { + chip_pw_fuzz_target("fuzz-chip-cert-pw") { + test_source = [ "FuzzChipCertPW.cpp" ] + public_deps = [ + "${chip_root}/src/credentials", + "${chip_root}/src/platform/logging:default", + ] + } +} diff --git a/src/credentials/tests/FuzzChipCertPW.cpp b/src/credentials/tests/FuzzChipCertPW.cpp new file mode 100644 index 00000000000000..bb929b392500d7 --- /dev/null +++ b/src/credentials/tests/FuzzChipCertPW.cpp @@ -0,0 +1,95 @@ +#include +#include + +#include +#include + +#include "credentials/CHIPCert.h" + +namespace { + +using namespace chip; +using namespace chip::Credentials; + +using namespace fuzztest; + +void ChipCertFuzzer(const std::vector & bytes) +{ + ByteSpan span(bytes.data(), bytes.size()); + + { + NodeId nodeId; + FabricId fabricId; + (void) ExtractFabricIdFromCert(span, &fabricId); + (void) ExtractNodeIdFabricIdFromOpCert(span, &nodeId, &fabricId); + } + + { + CATValues cats; + (void) ExtractCATsFromOpCert(span, cats); + } + + { + Credentials::P256PublicKeySpan key; + (void) ExtractPublicKeyFromChipCert(span, key); + } + + { + chip::System::Clock::Seconds32 rcacNotBefore; + (void) ExtractNotBeforeFromChipCert(span, rcacNotBefore); + } + + { + Credentials::CertificateKeyId skid; + (void) ExtractSKIDFromChipCert(span, skid); + } + + { + ChipDN subjectDN; + (void) ExtractSubjectDNFromChipCert(span, subjectDN); + } + + { + uint8_t outCertBuf[kMaxDERCertLength]; + MutableByteSpan outCert(outCertBuf); + (void) ConvertChipCertToX509Cert(span, outCert); + } + + { + // TODO: #35369 Move this to a Fixture once Errors related to FuzzTest Fixtures are resolved + ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR); + ValidateChipRCAC(span); + chip::Platform::MemoryShutdown(); + } +} + +FUZZ_TEST(FuzzChipCert, ChipCertFuzzer).WithDomains(Arbitrary>()); + +// The Property function for DecodeChipCertFuzzer, The FUZZ_TEST Macro will call this function. +void DecodeChipCertFuzzer(const std::vector & bytes, BitFlags aDecodeFlag) +{ + ByteSpan span(bytes.data(), bytes.size()); + + // TODO: #34352 To Move this to a Fixture once Errors related to FuzzTest Fixtures are resolved + ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR); + + ChipCertificateData certData; + (void) DecodeChipCert(span, certData, aDecodeFlag); + + chip::Platform::MemoryShutdown(); +} + +// This function allows us to fuzz using one of three CertDecodeFlags flags; by using FuzzTests's `ElementOf` API, we define an +// input domain by explicitly enumerating the set of values in it More Info: +// https://github.com/google/fuzztest/blob/main/doc/domains-reference.md#elementof-domains-element-of +auto AnyCertDecodeFlag() +{ + constexpr BitFlags NullDecodeFlag; + constexpr BitFlags GenTBSHashFlag(CertDecodeFlags::kGenerateTBSHash); + constexpr BitFlags TrustAnchorFlag(CertDecodeFlags::kIsTrustAnchor); + + return ElementOf({ NullDecodeFlag, GenTBSHashFlag, TrustAnchorFlag }); +} + +FUZZ_TEST(FuzzChipCert, DecodeChipCertFuzzer).WithDomains(Arbitrary>(), AnyCertDecodeFlag()); +} // namespace diff --git a/src/lib/core/tests/BUILD.gn b/src/lib/core/tests/BUILD.gn index eb17707dec1755..264de2c8aafe6b 100644 --- a/src/lib/core/tests/BUILD.gn +++ b/src/lib/core/tests/BUILD.gn @@ -59,3 +59,13 @@ if (enable_fuzz_test_targets) { ] } } + +if (pw_enable_fuzz_test_targets) { + chip_pw_fuzz_target("fuzz-tlv-reader-pw") { + test_source = [ "FuzzTlvReaderPW.cpp" ] + public_deps = [ + "${chip_root}/src/lib/core", + "${chip_root}/src/platform/logging:default", + ] + } +} diff --git a/src/lib/core/tests/FuzzTlvReaderPW.cpp b/src/lib/core/tests/FuzzTlvReaderPW.cpp new file mode 100644 index 00000000000000..4296cac3cffb02 --- /dev/null +++ b/src/lib/core/tests/FuzzTlvReaderPW.cpp @@ -0,0 +1,35 @@ + +#include +#include + +#include +#include + +#include "lib/core/TLV.h" +#include "lib/core/TLVUtilities.h" + +namespace { + +using chip::TLV::TLVReader; + +using namespace fuzztest; + +static CHIP_ERROR FuzzIterator(const TLVReader & aReader, size_t aDepth, void * aContext) +{ + aReader.GetLength(); + aReader.GetTag(); + aReader.GetType(); + return CHIP_NO_ERROR; +} + +// The Property Function +void FuzzTlvReader(const std::vector & bytes) +{ + TLVReader reader; + reader.Init(bytes.data(), bytes.size()); + chip::TLV::Utilities::Iterate(reader, FuzzIterator, nullptr); +} +// Fuzz tests are instantiated with the FUZZ_TEST macro +FUZZ_TEST(TLVReader, FuzzTlvReader).WithDomains(Arbitrary>()); + +} // namespace diff --git a/src/lib/dnssd/minimal_mdns/tests/BUILD.gn b/src/lib/dnssd/minimal_mdns/tests/BUILD.gn index 47e83650d41acc..457c11ee688b94 100644 --- a/src/lib/dnssd/minimal_mdns/tests/BUILD.gn +++ b/src/lib/dnssd/minimal_mdns/tests/BUILD.gn @@ -53,3 +53,13 @@ if (enable_fuzz_test_targets) { ] } } + +if (pw_enable_fuzz_test_targets) { + chip_pw_fuzz_target("fuzz-minmdns-packet-parsing-pw") { + test_source = [ "FuzzPacketParsingPW.cpp" ] + public_deps = [ + "${chip_root}/src/lib/dnssd/minimal_mdns", + "${chip_root}/src/platform/logging:default", + ] + } +} diff --git a/src/lib/dnssd/minimal_mdns/tests/FuzzPacketParsingPW.cpp b/src/lib/dnssd/minimal_mdns/tests/FuzzPacketParsingPW.cpp new file mode 100644 index 00000000000000..aec550b26e026a --- /dev/null +++ b/src/lib/dnssd/minimal_mdns/tests/FuzzPacketParsingPW.cpp @@ -0,0 +1,132 @@ +#include +#include + +#include +#include + +#include +#include + +namespace { + +using namespace fuzztest; +using namespace std; + +using namespace chip; +using namespace mdns::Minimal; + +class FuzzDelegate : public ParserDelegate +{ +public: + FuzzDelegate(const mdns::Minimal::BytesRange & packet) : mPacketRange(packet) {} + virtual ~FuzzDelegate() {} + + void OnHeader(ConstHeaderRef & header) override {} + void OnQuery(const QueryData & data) override {} + void OnResource(ResourceType type, const ResourceData & data) override + { + switch (data.GetType()) + { + case QType::SRV: { + mdns::Minimal::SrvRecord srv; + (void) srv.Parse(data.GetData(), mPacketRange); + break; + } + case QType::A: { + chip::Inet::IPAddress addr; + (void) mdns::Minimal::ParseARecord(data.GetData(), &addr); + break; + } + case QType::AAAA: { + chip::Inet::IPAddress addr; + (void) mdns::Minimal::ParseAAAARecord(data.GetData(), &addr); + break; + } + case QType::PTR: { + mdns::Minimal::SerializedQNameIterator name; + (void) mdns::Minimal::ParsePtrRecord(data.GetData(), mPacketRange, &name); + break; + } + default: + // nothing to do + break; + } + } + +private: + mdns::Minimal::BytesRange mPacketRange; +}; + +void PacketParserFuzz(const std::vector & bytes) +{ + BytesRange packet(bytes.data(), bytes.data() + bytes.size()); + FuzzDelegate delegate(packet); + + mdns::Minimal::ParsePacket(packet, &delegate); +} + +FUZZ_TEST(MinimalmDNS, PacketParserFuzz).WithDomains(Arbitrary>()); + +class TxtRecordAccumulator : public TxtRecordDelegate +{ +public: + using DataType = vector>; + + void OnRecord(const BytesRange & name, const BytesRange & value) override + { + mData.push_back(make_pair(AsString(name), AsString(value))); + } + + DataType & Data() { return mData; } + const DataType & Data() const { return mData; } + +private: + DataType mData; + + static string AsString(const BytesRange & range) + { + return string(reinterpret_cast(range.Start()), reinterpret_cast(range.End())); + } +}; + +// The Property Function +void TxtResponderFuzz(const std::vector & aRecord) +{ + + bool equal_sign_present = false; + auto equal_sign_pos = aRecord.end(); + + // This test is only giving a set of values, it can be gives more + vector prefixedRecord{ static_cast(aRecord.size()) }; + + prefixedRecord.insert(prefixedRecord.end(), aRecord.begin(), aRecord.end()); + + TxtRecordAccumulator accumulator; + + // The Function under Test, Check that the function does not Crash + ParseTxtRecord(BytesRange(prefixedRecord.data(), (&prefixedRecord.back() + 1)), &accumulator); + + for (auto it = aRecord.begin(); it != aRecord.end(); it++) + { + // if this is first `=` found in the fuzzed record + if ('=' == static_cast(*it) && false == equal_sign_present) + { + equal_sign_present = true; + equal_sign_pos = it; + } + } + + // The Fuzzed Input (record) needs to have at least two characters in order for ParseTxtRecord to do something + if (aRecord.size() > 1) + { + if (true == equal_sign_present) + { + std::string input_record_value(equal_sign_pos + 1, aRecord.end()); + EXPECT_EQ(accumulator.Data().at(0).second, input_record_value); + } + } +} + +FUZZ_TEST(MinimalmDNS, TxtResponderFuzz).WithDomains(Arbitrary>().WithMaxSize(254)); + +} // namespace diff --git a/src/lib/format/tests/BUILD.gn b/src/lib/format/tests/BUILD.gn index 840a2eede60c78..1ce417ea91cdc3 100644 --- a/src/lib/format/tests/BUILD.gn +++ b/src/lib/format/tests/BUILD.gn @@ -57,3 +57,18 @@ if (enable_fuzz_test_targets) { ] } } + +if (pw_enable_fuzz_test_targets) { + chip_pw_fuzz_target("fuzz-payload-decoder-pw") { + test_source = [ "FuzzPayloadDecoderPW.cpp" ] + public_deps = [ + "${chip_root}/src/controller/data_model:cluster-tlv-metadata", + "${chip_root}/src/lib/core", + "${chip_root}/src/lib/format:flat-tree", + "${chip_root}/src/lib/format:protocol-decoder", + "${chip_root}/src/lib/format:protocol-tlv-metadata", + "${chip_root}/src/lib/support", + "${chip_root}/src/platform/logging:stdio", + ] + } +} diff --git a/src/lib/format/tests/FuzzPayloadDecoderPW.cpp b/src/lib/format/tests/FuzzPayloadDecoderPW.cpp new file mode 100644 index 00000000000000..52b51b308d0c2a --- /dev/null +++ b/src/lib/format/tests/FuzzPayloadDecoderPW.cpp @@ -0,0 +1,73 @@ +/* + * + * Copyright (c) 2020-2021 Project CHIP Authors + * All rights reserved. + * + * 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. + */ +#include +#include +#include +#include + +#include +#include + +#include +#include + +namespace { + +using namespace chip::Decoders; +using namespace chip::FlatTree; +using namespace chip::TLV; +using namespace chip::TLVMeta; +using namespace fuzztest; + +// The Property Function; The FUZZ_TEST macro will call this function, with the fuzzed input domains +void RunDecodeFuzz(const std::vector & bytes, chip::Protocols::Id mProtocol, uint8_t mMessageType) +{ + + PayloadDecoderInitParams params; + params.SetProtocolDecodeTree(chip::TLVMeta::protocols_meta).SetClusterDecodeTree(chip::TLVMeta::clusters_meta); + + // Fuzzing with different Protocols + params.SetProtocol(mProtocol); + + // Fuzzing with different MessageTypes + params.SetMessageType(mMessageType); + chip::Decoders::PayloadDecoder<64, 128> decoder(params); + + chip::ByteSpan payload(bytes.data(), bytes.size()); + + decoder.StartDecoding(payload); + + PayloadEntry entry; + while (decoder.Next(entry)) + { + // Nothing to do ... + } +} + +// This function allows us to fuzz using one of four protocols; by using FuzzTests's `ElementOf` API, we define an +// input domain by explicitly enumerating the set of values in it More Info: +// https://github.com/google/fuzztest/blob/main/doc/domains-reference.md#elementof-domains-element-of +auto AnyProtocolID() +{ + return ElementOf({ chip::Protocols::SecureChannel::Id, chip::Protocols::InteractionModel::Id, chip::Protocols::BDX::Id, + chip::Protocols::UserDirectedCommissioning::Id }); +} + +FUZZ_TEST(PayloadDecoder, RunDecodeFuzz).WithDomains(Arbitrary>(), AnyProtocolID(), Arbitrary()); + +} // namespace diff --git a/src/setup_payload/tests/BUILD.gn b/src/setup_payload/tests/BUILD.gn index 75cdb8a71b0ad3..fc7bd6fe13c83c 100644 --- a/src/setup_payload/tests/BUILD.gn +++ b/src/setup_payload/tests/BUILD.gn @@ -56,3 +56,13 @@ if (enable_fuzz_test_targets) { ] } } + +if (pw_enable_fuzz_test_targets) { + chip_pw_fuzz_target("fuzz-setup-payload-base38-pw") { + test_source = [ "FuzzBase38PW.cpp" ] + public_deps = [ + "${chip_root}/src/platform/logging:stdio", + "${chip_root}/src/setup_payload", + ] + } +} diff --git a/src/setup_payload/tests/FuzzBase38PW.cpp b/src/setup_payload/tests/FuzzBase38PW.cpp new file mode 100644 index 00000000000000..b39db0905dfea0 --- /dev/null +++ b/src/setup_payload/tests/FuzzBase38PW.cpp @@ -0,0 +1,77 @@ +#include +#include +#include + +#include +#include + +#include "setup_payload/QRCodeSetupPayloadParser.h" +#include +#include + +namespace { + +using namespace fuzztest; +using namespace chip; + +// The property Function +void Base38DecodeFuzz(const std::vector & bytes) +{ + std::string base38EncodedString(reinterpret_cast(bytes.data()), bytes.size()); + std::vector decodedData; + + // Ignoring return value, because in general the data is garbage and won't decode properly. + // We're just testing that the decoder does not crash on the fuzzer-generated inputs. + chip::base38Decode(base38EncodedString, decodedData); +} + +// The invocation of the FuzzTest +FUZZ_TEST(Base38Decoder, Base38DecodeFuzz).WithDomains(Arbitrary>()); + +/* The property function for a base38 roundtrip Fuzzer. + * It starts by encoding the fuzzing value passed + * into Base38. The encoded value will then be decoded. + * + * The fuzzer verifies that the decoded value is the same + * as the one in input.*/ +void Base38RoundTripFuzz(const std::vector & bytes) +{ + + size_t outputSizeNeeded = base38EncodedLength(bytes.size()); + const size_t kMaxOutputSize = 512; + + ASSERT_LT(outputSizeNeeded, kMaxOutputSize); + + ByteSpan span(bytes.data(), bytes.size()); + + char encodedBuf[kMaxOutputSize]; + MutableCharSpan encodedSpan(encodedBuf); + CHIP_ERROR encodingError = base38Encode(span, encodedSpan); + ASSERT_EQ(encodingError, CHIP_NO_ERROR); + + std::string base38EncodedString(encodedSpan.data(), encodedSpan.size()); + + std::vector decodedData; + CHIP_ERROR decodingError = base38Decode(base38EncodedString, decodedData); + + ASSERT_EQ(decodingError, CHIP_NO_ERROR); + + // Make sure that decoded data is equal to the original fuzzed input; the bytes vector + ASSERT_EQ(decodedData, bytes); +} + +// Max size of the vector is defined as 306 since that will give an outputSizeNeeded of 511 which is less than the required +// kMaxOutputSize +FUZZ_TEST(Base38Decoder, Base38RoundTripFuzz).WithDomains(Arbitrary>().WithMaxSize(306)); + +void FuzzQRCodeSetupPayloadParser(const std::string & s) +{ + chip::Platform::MemoryInit(); + + SetupPayload payload; + QRCodeSetupPayloadParser(s).populatePayload(payload); +} + +FUZZ_TEST(Base38Decoder, FuzzQRCodeSetupPayloadParser).WithDomains(Arbitrary()); + +} // namespace diff --git a/third_party/abseil-cpp/src b/third_party/abseil-cpp/src new file mode 160000 index 00000000000000..3ab97e7212bff9 --- /dev/null +++ b/third_party/abseil-cpp/src @@ -0,0 +1 @@ +Subproject commit 3ab97e7212bff931a201c794fa1331960158bbfa diff --git a/third_party/fuzztest b/third_party/fuzztest new file mode 160000 index 00000000000000..6eb010c7223a6a --- /dev/null +++ b/third_party/fuzztest @@ -0,0 +1 @@ +Subproject commit 6eb010c7223a6aa609b94d49bfc06ac88f922961 diff --git a/third_party/googletest b/third_party/googletest new file mode 160000 index 00000000000000..1d17ea141d2c11 --- /dev/null +++ b/third_party/googletest @@ -0,0 +1 @@ +Subproject commit 1d17ea141d2c11b8917d2c7d029f1c4e2b9769b2 diff --git a/third_party/re2/src b/third_party/re2/src new file mode 160000 index 00000000000000..85dd7ad833a730 --- /dev/null +++ b/third_party/re2/src @@ -0,0 +1 @@ +Subproject commit 85dd7ad833a73095ecf3e3baea608ba051bbe2c7