diff --git a/src/app/WriteClient.cpp b/src/app/WriteClient.cpp index f903d4b168541f..755573571f1216 100644 --- a/src/app/WriteClient.cpp +++ b/src/app/WriteClient.cpp @@ -171,7 +171,7 @@ CHIP_ERROR WriteClient::StartNewMessage() ReturnErrorOnFailure(FinalizeMessage(true)); } - // Do not allow timed request with chunks. + // Per Matter specification: a Write Request that is part of a Timed Write Interaction SHALL NOT be chunked. VerifyOrReturnError(!(mTimedWriteTimeoutMs.HasValue() && !mChunks.IsNull()), CHIP_ERROR_NO_MEMORY); System::PacketBufferHandle packet = System::PacketBufferHandle::New(kMaxSecureSduLengthBytes); @@ -201,7 +201,7 @@ CHIP_ERROR WriteClient::StartNewMessage() ReturnErrorOnFailure(mWriteRequestBuilder.Init(&mMessageWriter)); mWriteRequestBuilder.SuppressResponse(mSuppressResponse); - mWriteRequestBuilder.TimedRequest(mTimedWriteTimeoutMs.HasValue()); + mWriteRequestBuilder.TimedRequest(mTimedRequestFieldValue); ReturnErrorOnFailure(mWriteRequestBuilder.GetError()); mWriteRequestBuilder.CreateWriteRequests(); ReturnErrorOnFailure(mWriteRequestBuilder.GetError()); diff --git a/src/app/WriteClient.h b/src/app/WriteClient.h index efa01c4a6c8cf8..648f710a896e82 100644 --- a/src/app/WriteClient.h +++ b/src/app/WriteClient.h @@ -129,7 +129,7 @@ class WriteClient : public Messaging::ExchangeDelegate bool aSuppressResponse = false) : mpExchangeMgr(apExchangeMgr), mExchangeCtx(*this), mpCallback(apCallback), mTimedWriteTimeoutMs(aTimedWriteTimeoutMs), - mSuppressResponse(aSuppressResponse) + mSuppressResponse(aSuppressResponse), mTimedRequestFieldValue(aTimedWriteTimeoutMs.HasValue()) { assertChipStackLockedByCurrentThread(); } @@ -138,7 +138,53 @@ class WriteClient : public Messaging::ExchangeDelegate WriteClient(Messaging::ExchangeManager * apExchangeMgr, Callback * apCallback, const Optional & aTimedWriteTimeoutMs, uint16_t aReservedSize) : mpExchangeMgr(apExchangeMgr), - mExchangeCtx(*this), mpCallback(apCallback), mTimedWriteTimeoutMs(aTimedWriteTimeoutMs), mReservedSize(aReservedSize) + mExchangeCtx(*this), mpCallback(apCallback), mTimedWriteTimeoutMs(aTimedWriteTimeoutMs), mReservedSize(aReservedSize), + mTimedRequestFieldValue(aTimedWriteTimeoutMs.HasValue()) + { + assertChipStackLockedByCurrentThread(); + } + + // Tag type to distinguish the test constructor from the normal constructor + struct TestOnlyOverrideTimedRequestFieldTag + { + }; + + /** + * TestOnly constructor that decouples the Timed Request action from the TimedRequest field value. + * + * IMPORTANT: Understanding the distinction between two concepts: + * 1. TIMED REQUEST ACTION: A preceding TimedRequest protocol message sent before the actual Write Request. + * This establishes a time window during which the server will accept the write. + * This is controlled by the mTimedWriteTimeoutMs field. + * + * 2. TIMEDREQUEST FIELD: A boolean field in the WriteRequest message itself that indicates whether + * the write was preceded by a Timed Request action. + * This is controlled by the mTimedRequestFieldValue field. + * + * Normal behavior: When you provide a timeout value to the standard constructor, both happen together: + * - A Timed Request action is sent (controlled by mTimedWriteTimeoutMs) + * - The TimedRequest field in WriteRequest is set to true (mTimedRequestFieldValue = true) + * + * This test constructor allows you to decouple these for testing all edge cases: + * + * Test scenarios enabled by this constructor: + * 1. Normal write (both false): Action = No, Field = False [aTimedWriteTimeoutMs = Missing, + * aTimedRequestFieldValue = false] + * 2. Normal timed write (both true): Action = Yes, Field = True [aTimedWriteTimeoutMs = value, + * aTimedRequestFieldValue = true] + * 3. Field true, no action (invalid): Action = No, Field = True [aTimedWriteTimeoutMs = Missing, + * aTimedRequestFieldValue = true] + * 4. Action present, field false (invalid): Action = Yes, Field = False [aTimedWriteTimeoutMs = value, + * aTimedRequestFieldValue = false] + * + * @param[in] aTimedWriteTimeoutMs The timeout for the Timed Request action (if provided, action WILL be sent) + * @param[in] aTimedRequestFieldValue The value of the TimedRequest field in WriteRequest (can mismatch the action for testing) + */ + WriteClient(Messaging::ExchangeManager * apExchangeMgr, Callback * apCallback, const Optional & aTimedWriteTimeoutMs, + bool aTimedRequestFieldValue, TestOnlyOverrideTimedRequestFieldTag) : + mpExchangeMgr(apExchangeMgr), + mExchangeCtx(*this), mpCallback(apCallback), mTimedWriteTimeoutMs(aTimedWriteTimeoutMs), + mTimedRequestFieldValue(aTimedRequestFieldValue) { assertChipStackLockedByCurrentThread(); } @@ -525,6 +571,15 @@ class WriteClient : public Messaging::ExchangeDelegate uint16_t mReservedSize = 0; // #endif + /** + * The value of the TimedRequest field in the WriteRequest message. + * + * This tells the server whether this write was preceded by a Timed Request action. + * Normally this matches whether mTimedWriteTimeoutMs has a value, but test constructors + * can decouple these to test protocol mismatch scenarios. + */ + bool mTimedRequestFieldValue = false; + /** * Below we define several const variables for encoding overheads. * WriteRequestMessage = 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 deleted file mode 100644 index 60e95f4a151e51..00000000000000 --- a/src/app/tests/suites/certification/Test_TC_IDM_3_2.yaml +++ /dev/null @@ -1,398 +0,0 @@ -# Copyright (c) 2021 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: 3.3.2. [TC-IDM-3.2] Write Response Action from DUT to TH. [DUT as Server] - -PICS: - - MCORE.IDM.S - -config: - nodeId: 0x12344321 - cluster: "Basic Information" - 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 Step 5 cannot be executed with the current 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, - DUT should send a write response action." - verification: | - 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 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 - [1686227268.758171][93527:93529] CHIP:DMG: WriteClient moving to [ResponseRe] - [1686227268.758192][93527:93529] CHIP:DMG: WriteResponseMessage = - [1686227268.758200][93527:93529] CHIP:DMG: { - [1686227268.758205][93527:93529] CHIP:DMG: AttributeStatusIBs = - [1686227268.758216][93527:93529] CHIP:DMG: [ - [1686227268.758222][93527:93529] CHIP:DMG: AttributeStatusIB = - [1686227268.758228][93527:93529] CHIP:DMG: { - [1686227268.758236][93527:93529] CHIP:DMG: AttributePathIB = - [1686227268.758245][93527:93529] CHIP:DMG: { - [1686227268.758253][93527:93529] CHIP:DMG: Endpoint = 0x1, - [1686227268.758261][93527:93529] CHIP:DMG: Cluster = 0x8, - [1686227268.758269][93527:93529] CHIP:DMG: Attribute = 0x0000_0011, - [1686227268.758276][93527:93529] CHIP:DMG: } - [1686227268.758287][93527:93529] CHIP:DMG: - [1686227268.758294][93527:93529] CHIP:DMG: StatusIB = - [1686227268.758307][93527:93529] CHIP:DMG: { - [1686227268.758315][93527:93529] CHIP:DMG: status = 0x00 (SUCCESS), - [1686227268.758322][93527:93529] CHIP:DMG: }, - [1686227268.758332][93527:93529] CHIP:DMG: - [1686227268.758337][93527:93529] CHIP:DMG: }, - [1686227268.758347][93527:93529] CHIP:DMG: - [1686227268.758353][93527:93529] CHIP:DMG: ], - [1686227268.758363][93527:93529] CHIP:DMG: - [1686227268.758369][93527:93529] CHIP:DMG: InteractionModelRevision = 1 - [1686227268.758375][93527:93529] CHIP:DMG: } - [1686227268.758410][93527:93529] CHIP:DMG: WriteClient moving to [AwaitingDe] - [1686227268.758462][93527:93529] CHIP:EM: <<< [E:46i S:53908 M:59594222 (Ack:246023482)] (S) Msg TX to 1:0000000000000001 [58B9] --- Type 0000:10 (SecureChannel:StandaloneAck) - [1686227268.758475][93527:93529] CHIP:IN: (S) Sending msg 59594222 on secure session with LSID: 53908 - - - ./chip-tool levelcontrol read on-level 1 1 - - On TH(chip-tool), verify the attribute value that was modified in above step - - [1686227336.578590][93540:93542] CHIP:DMG: } - [1686227336.578662][93540:93542] CHIP:TOO: Endpoint: 1 Cluster: 0x0000_0008 Attribute 0x0000_0011 DataVersion: 2737039617 - [1686227336.578696][93540:93542] CHIP:TOO: OnLevel: 2 - [1686227336.578749][93540:93542] CHIP:EM: <<< [E:46452i S:27133 M:147974322 (Ack:150508079)] (S) Msg TX to 1:0000000000000001 [58B9] --- Type 0000:10 (SecureChannel:StandaloneAck) - [1686227336.578761][93540:93542] CHIP:IN: (S) Sending msg 147974322 on secure session with LSID: 27133 - [1686227336.578788][93540:93542] CHIP:EM: Flushed pending ack for MessageCounter:150508079 on exchange 46452i - disabled: true - - - label: - "Step 2: TH sends the WriteRequestMessage to the DUT to write any - attribute on an unsupported Endpoint. DUT responds with the Write - Response action" - verification: | - 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 20 - - On TH(chip-tool), Verify that the DUT sends the status code UNSUPPORTED_ENDPOINT for the data sent in the above command - - [1686227733.922184][93629:93631] CHIP:DMG: WriteClient moving to [ResponseRe] - [1686227733.922210][93629:93631] CHIP:DMG: WriteResponseMessage = - [1686227733.922219][93629:93631] CHIP:DMG: { - [1686227733.922225][93629:93631] CHIP:DMG: AttributeStatusIBs = - [1686227733.922236][93629:93631] CHIP:DMG: [ - [1686227733.922242][93629:93631] CHIP:DMG: AttributeStatusIB = - [1686227733.922250][93629:93631] CHIP:DMG: { - [1686227733.922256][93629:93631] CHIP:DMG: AttributePathIB = - [1686227733.922264][93629:93631] CHIP:DMG: { - [1686227733.922272][93629:93631] CHIP:DMG: Endpoint = 0x14, - [1686227733.922279][93629:93631] CHIP:DMG: Cluster = 0x8, - [1686227733.922287][93629:93631] CHIP:DMG: Attribute = 0x0000_0011, - [1686227733.922293][93629:93631] CHIP:DMG: } - [1686227733.922304][93629:93631] CHIP:DMG: - [1686227733.922310][93629:93631] CHIP:DMG: StatusIB = - [1686227733.922318][93629:93631] CHIP:DMG: { - [1686227733.922325][93629:93631] CHIP:DMG: status = 0x7f (UNSUPPORTED_ENDPOINT), - [1686227733.922332][93629:93631] CHIP:DMG: }, - [1686227733.922339][93629:93631] CHIP:DMG: - [1686227733.922345][93629:93631] CHIP:DMG: }, - [1686227733.922354][93629:93631] CHIP:DMG: - [1686227733.922359][93629:93631] CHIP:DMG: ], - [1686227733.922369][93629:93631] CHIP:DMG: - [1686227733.922375][93629:93631] CHIP:DMG: InteractionModelRevision = 1 - [1686227733.922381][93629:93631] CHIP:DMG: } - [1686227733.922415][93629:93631] CHIP:DMG: WriteClient moving to [AwaitingDe] - [1686227733.922426][93629:93631] CHIP:TOO: Response Failure: IM Error 0x0000057F: General error: 0x7f (UNSUPPORTED_ENDPOINT) - [1686227733.922472][93629:93631] CHIP:EM: <<< [E:27399i S:27512 M:260380598 (Ack:50253556)] (S) Msg TX to 1:0000000000000001 [58B9] --- Type 0000:10 (SecureChannel:StandaloneAck) - [1686227733.922485][93629:93631] CHIP:IN: (S) Sending msg 260380598 on secure session with LSID: 27512 - disabled: true - - - label: - "Step 3: TH sends the WriteRequestMessage to the DUT to write any - attribute on an unsupported cluster. DUT responds with the Write - Response action" - verification: | - The cluster used in the below command is an example, User can use any supported chip cluster/attribute/command. - - ./chip-tool thermostat write unoccupied-heating-setpoint 1200 1 0 - - 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 = - [1686229393.094041][94065:94067] CHIP:DMG: { - [1686229393.094047][94065:94067] CHIP:DMG: AttributeStatusIBs = - [1686229393.094058][94065:94067] CHIP:DMG: [ - [1686229393.094064][94065:94067] CHIP:DMG: AttributeStatusIB = - [1686229393.094072][94065:94067] CHIP:DMG: { - [1686229393.094079][94065:94067] CHIP:DMG: AttributePathIB = - [1686229393.094093][94065:94067] CHIP:DMG: { - [1686229393.094096][94065:94067] CHIP:DMG: Endpoint = 0x0, - [1686229393.094098][94065:94067] CHIP:DMG: Cluster = 0x201, - [1686229393.094101][94065:94067] CHIP:DMG: Attribute = 0x0000_0014, - [1686229393.094103][94065:94067] CHIP:DMG: } - [1686229393.094106][94065:94067] CHIP:DMG: - [1686229393.094108][94065:94067] CHIP:DMG: StatusIB = - [1686229393.094110][94065:94067] CHIP:DMG: { - [1686229393.094112][94065:94067] CHIP:DMG: status = 0xc3 (UNSUPPORTED_CLUSTER), - [1686229393.094114][94065:94067] CHIP:DMG: }, - [1686229393.094117][94065:94067] CHIP:DMG: - [1686229393.094118][94065:94067] CHIP:DMG: }, - [1686229393.094121][94065:94067] CHIP:DMG: - [1686229393.094123][94065:94067] CHIP:DMG: ], - [1686229393.094126][94065:94067] CHIP:DMG: - [1686229393.094128][94065:94067] CHIP:DMG: InteractionModelRevision = 1 - [1686229393.094129][94065:94067] CHIP:DMG: } - [1686229393.094140][94065:94067] CHIP:DMG: WriteClient moving to [AwaitingDe] - [1686229393.094144][94065:94067] CHIP:TOO: Response Failure: IM Error 0x000005C3: General error: 0xc3 (UNSUPPORTED_CLUSTER) - [1686229393.094163][94065:94067] CHIP:EM: <<< [E:60736i S:49727 M:56487502 (Ack:119439349)] (S) Msg TX to 1:0000000000000001 [58B9] --- Type 0000:10 (SecureChannel:StandaloneAck) - [1686229393.094167][94065:94067] CHIP:IN: (S) Sending msg 56487502 on secure session with LSID: 49727 - disabled: true - - - label: - "Step 4: TH sends the WriteRequestMessage to the DUT to write an - unsupported attribute DUT responds with the Write Response action" - verification: | - The cluster used in the below command is an example, User can use any supported chip cluster/attribute/command. - - ./chip-tool thermostat write unoccupied-heating-setpoint 1200 1 1 - - On TH(chip-tool), Verify that the DUT sends the status code UNSUPPORTED_ATTRIBUTE for the data sent in the above command - - [1686228971.778055][93969:93971] CHIP:DMG: WriteResponseMessage = - [1686228971.778062][93969:93971] CHIP:DMG: { - [1686228971.778067][93969:93971] CHIP:DMG: AttributeStatusIBs = - [1686228971.778078][93969:93971] CHIP:DMG: [ - [1686228971.778084][93969:93971] CHIP:DMG: AttributeStatusIB = - [1686228971.778091][93969:93971] CHIP:DMG: { - [1686228971.778097][93969:93971] CHIP:DMG: AttributePathIB = - [1686228971.778107][93969:93971] CHIP:DMG: { - [1686228971.778116][93969:93971] CHIP:DMG: Endpoint = 0x1, - [1686228971.778125][93969:93971] CHIP:DMG: Cluster = 0x201, - [1686228971.778133][93969:93971] CHIP:DMG: Attribute = 0x0000_0014, - [1686228971.778140][93969:93971] CHIP:DMG: } - [1686228971.778150][93969:93971] CHIP:DMG: - [1686228971.778157][93969:93971] CHIP:DMG: StatusIB = - [1686228971.778167][93969:93971] CHIP:DMG: { - [1686228971.778175][93969:93971] CHIP:DMG: status = 0x86 (UNSUPPORTED_ATTRIBUTE), - [1686228971.778182][93969:93971] CHIP:DMG: }, - [1686228971.778193][93969:93971] CHIP:DMG: - [1686228971.778199][93969:93971] CHIP:DMG: }, - [1686228971.778210][93969:93971] CHIP:DMG: - [1686228971.778216][93969:93971] CHIP:DMG: ], - [1686228971.778227][93969:93971] CHIP:DMG: - [1686228971.778233][93969:93971] CHIP:DMG: InteractionModelRevision = 1 - [1686228971.778241][93969:93971] CHIP:DMG: } - [1686228971.778277][93969:93971] CHIP:DMG: WriteClient moving to [AwaitingDe] - [1686228971.778288][93969:93971] CHIP:TOO: Response Failure: IM Error 0x00000586: General error: 0x86 (UNSUPPORTED_ATTRIBUTE) - [1686228971.778336][93969:93971] CHIP:EM: <<< [E:10343i S:64864 M:48035208 (Ack:227000243)] (S) Msg TX to 1:0000000000000001 [58B9] --- Type 0000:10 (SecureChannel:StandaloneAck) - [1686228971.778349][93969:93971] CHIP:IN: (S) Sending msg 48035208 on secure session with LSID: 64864 - disabled: true - - - label: - "Step 5: TH sends the WriteRequestMessage to the DUT to modify the - value of one attribute and Set SuppressResponse to True. +" - verification: | - Out of Scope - https://github.com/project-chip/connectedhomeip/issues/8043 - disabled: true - - - label: - "Step 6: TH sends a ReadRequest message to the DUT to read any - attribute on any cluster. DUT returns with a report data action with - the attribute values and the dataversion of the cluster. TH sends a - WriteRequestMessage to the DUT to modify the value of one attribute - with the DataVersion field set to the one received in the prior step. - +" - 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 the attribute value which is received from DUT - [1686229541.147770][94111:94113] CHIP:DMG: - [1686229541.147777][94111:94113] CHIP:DMG: SuppressResponse = true, - [1686229541.147784][94111:94113] CHIP:DMG: InteractionModelRevision = 1 - [1686229541.147789][94111:94113] CHIP:DMG: } - [1686229541.147862][94111:94113] CHIP:TOO: Endpoint: 1 Cluster: 0x0000_0008 Attribute 0x0000_0011 DataVersion: 2737039621 - [1686229541.147890][94111:94113] CHIP:TOO: OnLevel: 1 - [1686229541.147948][94111:94113] CHIP:EM: <<< [E:19153i S:22252 M:202945427 (Ack:256959089)] (S) Msg TX to 1:0000000000000001 [58B9] --- Type 0000:10 (SecureChannel:StandaloneAck) - [1686229541.147961][94111:94113] CHIP:IN: (S) Sending msg 202945427 on secure session with LSID: 22252 - - - - TH sends below mentioned WriteRequestMessage to the DUT to modify the value of one attribute with the DataVersion field set to the one received in the prior step. - - ./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: { - [1686229590.858827][94124:94126] CHIP:DMG: AttributeStatusIBs = - [1686229590.858841][94124:94126] CHIP:DMG: [ - [1686229590.858847][94124:94126] CHIP:DMG: AttributeStatusIB = - [1686229590.858855][94124:94126] CHIP:DMG: { - [1686229590.858862][94124:94126] CHIP:DMG: AttributePathIB = - [1686229590.858870][94124:94126] CHIP:DMG: { - [1686229590.858879][94124:94126] CHIP:DMG: Endpoint = 0x1, - [1686229590.858886][94124:94126] CHIP:DMG: Cluster = 0x8, - [1686229590.858894][94124:94126] CHIP:DMG: Attribute = 0x0000_0011, - [1686229590.858901][94124:94126] CHIP:DMG: } - [1686229590.858911][94124:94126] CHIP:DMG: - [1686229590.858918][94124:94126] CHIP:DMG: StatusIB = - [1686229590.858926][94124:94126] CHIP:DMG: { - [1686229590.858934][94124:94126] CHIP:DMG: status = 0x00 (SUCCESS), - [1686229590.858941][94124:94126] CHIP:DMG: }, - [1686229590.858948][94124:94126] CHIP:DMG: - [1686229590.858955][94124:94126] CHIP:DMG: }, - [1686229590.858964][94124:94126] CHIP:DMG: - [1686229590.858970][94124:94126] CHIP:DMG: ], - [1686229590.858981][94124:94126] CHIP:DMG: - [1686229590.858988][94124:94126] CHIP:DMG: InteractionModelRevision = 1 - [1686229590.858993][94124:94126] CHIP:DMG: } - [1686229590.859027][94124:94126] CHIP:DMG: WriteClient moving to [AwaitingDe] - [1686229590.859070][94124:94126] CHIP:EM: <<< [E:18131i S:14478 M:103762576 (Ack:226584667)] (S) Msg TX to 1:0000000000000001 [58B9] --- Type 0000:10 (SecureChannel:StandaloneAck) - [1686229590.859083][94124:94126] CHIP:IN: (S) Sending msg 103762576 on secure session with LSID: 14478 - - - ./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 - [1686229613.307719][94130:94132] CHIP:EM: <<< [E:28271i S:6514 M:63187593 (Ack:74885751)] (S) Msg TX to 1:0000000000000001 [58B9] --- Type 0000:10 (SecureChannel:StandaloneAck) - [1686229613.307737][94130:94132] CHIP:IN: (S) Sending msg 63187593 on secure session with LSID: 6514 - [1686229613.307776][94130:94132] CHIP:EM: Flushed pending ack for MessageCounter:74885751 on exchange 28271i - disabled: true - - - label: - "Step 7: TH sends a ReadRequest message to the DUT to read any - attribute on any cluster. DUT returns with a report data action with - the attribute values and the dataversion of the cluster. TH sends a - WriteRequestMessage to the DUT to modify the value of one attribute no - DataVersion indicated. TH sends a second WriteRequestMessage to the - DUT to modify the value of an attribute with the dataversion field set - to the value received earlier." - 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 - - [1686229658.938062][94141:94143] CHIP:DMG: InteractionModelRevision = 1 - [1686229658.938067][94141:94143] CHIP:DMG: } - [1686229658.938140][94141:94143] CHIP:TOO: Endpoint: 1 Cluster: 0x0000_0008 Attribute 0x0000_0011 DataVersion: 2737039622 - [1686229658.938175][94141:94143] CHIP:TOO: OnLevel: 3 - [1686229658.938226][94141:94143] CHIP:EM: <<< [E:14766i S:48999 M:254860411 (Ack:194137529)] (S) Msg TX to 1:0000000000000001 [58B9] --- Type 0000:10 (SecureChannel:StandaloneAck) - [1686229658.938238][94141:94143] CHIP:IN: (S) Sending msg 254860411 on secure session with LSID: 48999 - [1686229658.938255][94141:94143] CHIP:EM: Flushed pending ack for MessageCounter:194137529 on exchange 14766i - [1686229658.938307][94141:94141] CHIP:CTL: Shutting down the commissioner - - - ./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 = - [1686229675.214410][94147:94149] CHIP:DMG: [ - [1686229675.214418][94147:94149] CHIP:DMG: AttributeStatusIB = - [1686229675.214429][94147:94149] CHIP:DMG: { - [1686229675.214439][94147:94149] CHIP:DMG: AttributePathIB = - [1686229675.214451][94147:94149] CHIP:DMG: { - [1686229675.214462][94147:94149] CHIP:DMG: Endpoint = 0x1, - [1686229675.214473][94147:94149] CHIP:DMG: Cluster = 0x8, - [1686229675.214484][94147:94149] CHIP:DMG: Attribute = 0x0000_0011, - [1686229675.214494][94147:94149] CHIP:DMG: } - [1686229675.214509][94147:94149] CHIP:DMG: - [1686229675.214518][94147:94149] CHIP:DMG: StatusIB = - [1686229675.214530][94147:94149] CHIP:DMG: { - [1686229675.214540][94147:94149] CHIP:DMG: status = 0x00 (SUCCESS), - [1686229675.214550][94147:94149] CHIP:DMG: }, - [1686229675.214562][94147:94149] CHIP:DMG: - [1686229675.214570][94147:94149] CHIP:DMG: }, - [1686229675.214585][94147:94149] CHIP:DMG: - [1686229675.214592][94147:94149] CHIP:DMG: ], - [1686229675.214607][94147:94149] CHIP:DMG: - [1686229675.214615][94147:94149] CHIP:DMG: InteractionModelRevision = 1 - [1686229675.214623][94147:94149] CHIP:DMG: } - [1686229675.214664][94147:94149] CHIP:DMG: WriteClient moving to [AwaitingDe] - [1686229675.214705][94147:94149] CHIP:EM: <<< [E:50993i S:28788 M:7052880 (Ack:74458729)] (S) Msg TX to 1:0000000000000001 [58B9] --- Type 0000:10 (SecureChannel:StandaloneAck) - [1686229675.214719][94147:94149] CHIP:IN: (S) Sending msg 7052880 on secure session with LSID: 28788 - [1686229675.214749][94147:94149] CHIP:EM: Flushed pending ack for MessageCounter:74458729 on exchange 50993i - - - - TH sends below mentioned second WriteRequestMessage to the DUT to modify the value of an attribute with the dataversion field set to the value received earlier. - - ./chip-tool levelcontrol write on-level 4 1 1 --data-version 0xc4c9d7af - - On TH(chip-tool), verify that DUT responds as DATA_VERSION_MISMATCH for the second Write request. - - [1686229718.873144][94160:94162] CHIP:EM: Rxd Ack; Removing MessageCounter:265995501 from Retrans Table on exchange 34809i - [1686229718.873148][94160:94162] CHIP:DMG: WriteClient moving to [ResponseRe] - [1686229718.873157][94160:94162] CHIP:DMG: WriteResponseMessage = - [1686229718.873160][94160:94162] CHIP:DMG: { - [1686229718.873162][94160:94162] CHIP:DMG: AttributeStatusIBs = - [1686229718.873165][94160:94162] CHIP:DMG: [ - [1686229718.873167][94160:94162] CHIP:DMG: AttributeStatusIB = - [1686229718.873170][94160:94162] CHIP:DMG: { - [1686229718.873172][94160:94162] CHIP:DMG: AttributePathIB = - [1686229718.873175][94160:94162] CHIP:DMG: { - [1686229718.873177][94160:94162] CHIP:DMG: Endpoint = 0x1, - [1686229718.873179][94160:94162] CHIP:DMG: Cluster = 0x8, - [1686229718.873182][94160:94162] CHIP:DMG: Attribute = 0x0000_0011, - [1686229718.873184][94160:94162] CHIP:DMG: } - [1686229718.873187][94160:94162] CHIP:DMG: - [1686229718.873189][94160:94162] CHIP:DMG: StatusIB = - [1686229718.873192][94160:94162] CHIP:DMG: { - [1686229718.873194][94160:94162] CHIP:DMG: status = 0x92 (DATA_VERSION_MISMATCH), - [1686229718.873196][94160:94162] CHIP:DMG: }, - [1686229718.873199][94160:94162] CHIP:DMG: - [1686229718.873201][94160:94162] CHIP:DMG: }, - [1686229718.873203][94160:94162] CHIP:DMG: - [1686229718.873205][94160:94162] CHIP:DMG: ], - [1686229718.873208][94160:94162] CHIP:DMG: - [1686229718.873210][94160:94162] CHIP:DMG: InteractionModelRevision = 1 - [1686229718.873212][94160:94162] CHIP:DMG: } - [1686229718.873223][94160:94162] CHIP:DMG: WriteClient moving to [AwaitingDe] - [1686229718.873227][94160:94162] CHIP:TOO: Response Failure: IM Error 0x00000592: General error: 0x92 (DATA_VERSION_MISMATCH) - [1686229718.873244][94160:94162] CHIP:EM: <<< [E:34809i S:30372 M:265995502 (Ack:261218993)] (S) Msg TX to 1:0000000000000001 [58B9] --- Type 0000:10 (SecureChannel:StandaloneAck) - [1686229718.873248][94160:94162] CHIP:IN: (S) Sending msg 265995502 on secure session with LSID: 30372 - disabled: true - - - label: - "Step 8: TH sends the WriteRequestMessage to the DUT to modify the - value of a specific attribute data that needs Timed Write transaction - to write and this action is not part of a Timed Write transaction." - verification: | - DUT implementation required to verify write an attribute which need NEEDS_TIMED_INTERACTION. - - If the Vendor DUT doesn't implement/supported this attribute, Please mark the test step as "Not Applicable" - disabled: true diff --git a/src/app/tests/suites/manualTests.json b/src/app/tests/suites/manualTests.json index 3cd2050ec04720..0aa6b520ded8fb 100644 --- a/src/app/tests/suites/manualTests.json +++ b/src/app/tests/suites/manualTests.json @@ -113,7 +113,6 @@ "Test_TC_IDM_2_1", "Test_TC_IDM_2_2", "Test_TC_IDM_3_1", - "Test_TC_IDM_3_2", "Test_TC_IDM_4_1", "Test_TC_IDM_4_3", "Test_TC_IDM_4_4", diff --git a/src/controller/python/matter/ChipDeviceCtrl.py b/src/controller/python/matter/ChipDeviceCtrl.py index 019389def46cc2..7a1ee38b1e5c91 100644 --- a/src/controller/python/matter/ChipDeviceCtrl.py +++ b/src/controller/python/matter/ChipDeviceCtrl.py @@ -1607,6 +1607,77 @@ async def TestOnlySendCommandTimedRequestFlagWithNoTimedInvoke(self, nodeId: int ), payload).raise_on_error() return await future + def _prepare_write_attribute_requests( + self, + attributes: typing.List[typing.Union[ + typing.Tuple[int, ClusterObjects.ClusterAttributeDescriptor], + typing.Tuple[int, ClusterObjects.ClusterAttributeDescriptor, int] + ]] + ) -> typing.List[ClusterAttribute.AttributeWriteRequest]: + """Helper method to prepare attribute write requests.""" + attrs = [] + for v in attributes: + if len(v) == 2: + attrs.append(ClusterAttribute.AttributeWriteRequest( + EndpointId=v[0], + Attribute=v[1], + DataVersion=0, + HasDataVersion=0, + Data=v[1].value)) # type: ignore[attr-defined] + else: + attrs.append(ClusterAttribute.AttributeWriteRequest( + EndpointId=v[0], + Attribute=v[1], + DataVersion=v[2], + HasDataVersion=1, + Data=v[1].value)) + return attrs + + async def TestOnlyWriteAttributeWithMismatchedTimedRequestField(self, nodeid: int, + attributes: typing.List[typing.Union[ + typing.Tuple[int, + ClusterObjects.ClusterAttributeDescriptor], + typing.Tuple[int, + ClusterObjects.ClusterAttributeDescriptor, int] + ]], + timedRequestTimeoutMs: int, + timedRequestFieldValue: bool, + interactionTimeoutMs: typing.Optional[int] = None, busyWaitMs: typing.Optional[int] = None, + payloadCapability: int = TransportPayloadCapability.MRP_PAYLOAD): + ''' + ONLY TO BE USED FOR TEST: Write attributes with decoupled Timed Request action and TimedRequest field. + This allows testing TIMED_REQUEST_MISMATCH scenarios: + - timedRequestTimeoutMs=0, timedRequestFieldValue=True: No action, but field=true (MISMATCH) + - timedRequestTimeoutMs>0, timedRequestFieldValue=False: Action sent, but field=false (MISMATCH) + + Please see WriteAttribute for description of common parameters. + + Additional parameters: + timedRequestTimeoutMs: Timeout for the Timed Request action (0 means no action) + timedRequestFieldValue: Value of the TimedRequest field in WriteRequest + + Returns: + [AttributeStatus] (list - one for each path). + + Raises: + InteractionModelError on error (expected: TIMED_REQUEST_MISMATCH) + ''' + self.CheckIsActive() + + eventLoop = asyncio.get_running_loop() + future = eventLoop.create_future() + + device = await self.GetConnectedDevice(nodeid, timeoutMs=interactionTimeoutMs, payloadCapability=payloadCapability) + + attrs = self._prepare_write_attribute_requests(attributes) + + ClusterAttribute.TestOnlyWriteAttributeWithMismatchedTimedRequestField( + future, eventLoop, device.deviceProxy, attrs, + timedRequestTimeoutMs=timedRequestTimeoutMs, + timedRequestFieldValue=timedRequestFieldValue, + interactionTimeoutMs=interactionTimeoutMs, busyWaitMs=busyWaitMs).raise_on_error() + return await future + async def SendCommand(self, nodeId: int, endpoint: int, payload: ClusterObjects.ClusterCommand, responseType=None, timedRequestTimeoutMs: typing.Optional[int] = None, interactionTimeoutMs: typing.Optional[int] = None, busyWaitMs: typing.Optional[int] = None, @@ -1707,7 +1778,10 @@ def SendGroupCommand(self, groupid: int, payload: ClusterObjects.ClusterCommand, return None async def WriteAttribute(self, nodeId: int, - attributes: typing.List[typing.Tuple[int, ClusterObjects.ClusterAttributeDescriptor]], + attributes: typing.List[typing.Union[ + typing.Tuple[int, ClusterObjects.ClusterAttributeDescriptor], + typing.Tuple[int, ClusterObjects.ClusterAttributeDescriptor, int] + ]], timedRequestTimeoutMs: typing.Optional[int] = None, interactionTimeoutMs: typing.Optional[int] = None, busyWaitMs: typing.Optional[int] = None, payloadCapability: int = TransportPayloadCapability.MRP_PAYLOAD): @@ -1719,7 +1793,6 @@ async def WriteAttribute(self, nodeId: int, attributes: A list of tuples of type (endpoint, cluster-object): interactionTimeoutMs: Overall timeout for the interaction. Omit or set to 'None' to have the SDK automatically compute the right timeout value based on transport characteristics as well as the responsiveness of the target. - E.g (1, Clusters.UnitTesting.Attributes.XYZAttribute('hello')) -- Write 'hello' to the XYZ attribute on the test cluster to endpoint 1 @@ -1740,7 +1813,10 @@ async def WriteAttribute(self, nodeId: int, forceLegacyListEncoding=False) async def _WriteAttribute(self, nodeId: int, - attributes: typing.List[typing.Tuple[int, ClusterObjects.ClusterAttributeDescriptor]], + attributes: typing.List[typing.Union[ + typing.Tuple[int, ClusterObjects.ClusterAttributeDescriptor], + typing.Tuple[int, ClusterObjects.ClusterAttributeDescriptor, int] + ]], timedRequestTimeoutMs: typing.Optional[int] = None, interactionTimeoutMs: typing.Optional[int] = None, busyWaitMs: typing.Optional[int] = None, payloadCapability: int = TransportPayloadCapability.MRP_PAYLOAD, forceLegacyListEncoding: bool = False): @@ -1752,14 +1828,7 @@ async def _WriteAttribute(self, nodeId: int, device = await self.GetConnectedDevice(nodeId, timeoutMs=interactionTimeoutMs, payloadCapability=payloadCapability) - attrs = [] - for v in attributes: - if len(v) == 2: - attrs.append(ClusterAttribute.AttributeWriteRequest( - v[0], v[1], 0, 0, v[1].value)) # type: ignore[attr-defined] # 'value' added dynamically to ClusterAttributeDescriptor - else: - attrs.append(ClusterAttribute.AttributeWriteRequest( - v[0], v[1], v[2], 1, v[1].value)) + attrs = self._prepare_write_attribute_requests(attributes) ClusterAttribute.WriteAttributes( future, eventLoop, device.deviceProxy, attrs, timedRequestTimeoutMs=timedRequestTimeoutMs, @@ -1767,7 +1836,10 @@ async def _WriteAttribute(self, nodeId: int, return await future async def TestOnlyWriteAttributeWithLegacyList(self, nodeId: int, - attributes: typing.List[typing.Tuple[int, ClusterObjects.ClusterAttributeDescriptor]], + attributes: typing.List[typing.Union[ + typing.Tuple[int, ClusterObjects.ClusterAttributeDescriptor], + typing.Tuple[int, ClusterObjects.ClusterAttributeDescriptor, int] + ]], timedRequestTimeoutMs: typing.Optional[int] = None, interactionTimeoutMs: typing.Optional[int] = None, busyWaitMs: typing.Optional[int] = None, payloadCapability: int = TransportPayloadCapability.MRP_PAYLOAD): diff --git a/src/controller/python/matter/clusters/Attribute.py b/src/controller/python/matter/clusters/Attribute.py index d8c6b1ca5ab149..1cd8ba6bc6cc5f 100644 --- a/src/controller/python/matter/clusters/Attribute.py +++ b/src/controller/python/matter/clusters/Attribute.py @@ -1010,35 +1010,39 @@ def _OnWriteDoneCallback(closure): closure.handleDone() -def WriteAttributes(future: Future, eventLoop, device, - attributes: List[AttributeWriteRequest], timedRequestTimeoutMs: Union[None, int] = None, - interactionTimeoutMs: Union[None, int] = None, busyWaitMs: Union[None, int] = None, forceLegacyListEncoding: bool = False) -> PyChipError: - handle = GetLibraryHandle() - +def _prepare_write_attributes_data(attributes: List[AttributeWriteRequest], must_use_timed_write_check: bool = True, timedRequestTimeoutMs: Union[None, int] = None) -> Tuple[ctypes.Array[PyWriteAttributeData], int]: + """Helper function to prepare PyWriteAttributeData array from AttributeWriteRequest list.""" numberOfAttributes = len(attributes) pyWriteAttributesArrayType = PyWriteAttributeData * numberOfAttributes pyWriteAttributes = pyWriteAttributesArrayType() + for idx, attr in enumerate(attributes): - if attr.Attribute.must_use_timed_write and timedRequestTimeoutMs is None or timedRequestTimeoutMs == 0: + if must_use_timed_write_check and attr.Attribute.must_use_timed_write and timedRequestTimeoutMs is None or timedRequestTimeoutMs == 0: raise InteractionModelError( InteractionModelStatus.NeedsTimedInteraction) tlv = attr.Attribute.ToTLV(None, attr.Data) - pyWriteAttributes[idx].attributePath.endpointId = c_uint16( - attr.EndpointId) - pyWriteAttributes[idx].attributePath.clusterId = c_uint32( - attr.Attribute.cluster_id) - pyWriteAttributes[idx].attributePath.attributeId = c_uint32( - attr.Attribute.attribute_id) - pyWriteAttributes[idx].attributePath.dataVersion = c_uint32( - attr.DataVersion) - pyWriteAttributes[idx].attributePath.hasDataVersion = c_uint8( - attr.HasDataVersion) - pyWriteAttributes[idx].tlvData = cast( - ctypes.c_char_p(bytes(tlv)), c_void_p) + pyWriteAttributes[idx].attributePath.endpointId = c_uint16(attr.EndpointId) + pyWriteAttributes[idx].attributePath.clusterId = c_uint32(attr.Attribute.cluster_id) + pyWriteAttributes[idx].attributePath.attributeId = c_uint32(attr.Attribute.attribute_id) + pyWriteAttributes[idx].attributePath.dataVersion = c_uint32(attr.DataVersion) + pyWriteAttributes[idx].attributePath.hasDataVersion = c_uint8(attr.HasDataVersion) + pyWriteAttributes[idx].tlvData = cast(ctypes.c_char_p(bytes(tlv)), c_void_p) pyWriteAttributes[idx].tlvLength = c_size_t(len(tlv)) + return pyWriteAttributes, numberOfAttributes + + +def WriteAttributes(future: Future, eventLoop, device, + attributes: List[AttributeWriteRequest], timedRequestTimeoutMs: Union[None, int] = None, + interactionTimeoutMs: Union[None, int] = None, busyWaitMs: Union[None, int] = None, + forceLegacyListEncoding: bool = False) -> PyChipError: + handle = GetLibraryHandle() + + pyWriteAttributes, numberOfAttributes = _prepare_write_attributes_data( + attributes, must_use_timed_write_check=True, timedRequestTimeoutMs=timedRequestTimeoutMs) + transaction = AsyncWriteTransaction(future, eventLoop) ctypes.pythonapi.Py_IncRef(ctypes.py_object(transaction)) res = builtins.chipStack.Call( @@ -1057,6 +1061,43 @@ def WriteAttributes(future: Future, eventLoop, device, return res +def TestOnlyWriteAttributeWithMismatchedTimedRequestField(future: Future, eventLoop, device, + attributes: List[AttributeWriteRequest], + timedRequestTimeoutMs: int, + timedRequestFieldValue: bool, + interactionTimeoutMs: Union[None, int] = None, + busyWaitMs: Union[None, int] = None) -> PyChipError: + ''' + ONLY TO BE USED FOR TEST: Writes attributes with decoupled Timed Request action and TimedRequest field. + This allows testing TIMED_REQUEST_MISMATCH scenarios: + - timedRequestTimeoutMs=0, timedRequestFieldValue=True: No action, but field=true (MISMATCH) + - timedRequestTimeoutMs>0, timedRequestFieldValue=False: Action sent, but field=false (MISMATCH) + ''' + handle = GetLibraryHandle() + + # Note: We skip the timed write check here to allow testing the TIMED_REQUEST_MISMATCH scenario + # In normal WriteAttributes, this would check for must_use_timed_write + pyWriteAttributes, numberOfAttributes = _prepare_write_attributes_data( + attributes, must_use_timed_write_check=False) + + transaction = AsyncWriteTransaction(future, eventLoop) + ctypes.pythonapi.Py_IncRef(ctypes.py_object(transaction)) + + # Call the TestOnly C++ function that decouples action and field + res = builtins.chipStack.Call( + lambda: handle.pychip_WriteClient_TestOnlyWriteAttributesWithMismatchedTimedRequestField( + ctypes.py_object(transaction), device, + ctypes.c_size_t(timedRequestTimeoutMs), + ctypes.c_bool(timedRequestFieldValue), + ctypes.c_size_t(0 if interactionTimeoutMs is None else interactionTimeoutMs), + ctypes.c_size_t(0 if busyWaitMs is None else busyWaitMs), + pyWriteAttributes, ctypes.c_size_t(numberOfAttributes)) + ) + if not res.is_success: + ctypes.pythonapi.Py_DecRef(ctypes.py_object(transaction)) + return res + + def WriteGroupAttributes(groupId: int, devCtrl: c_void_p, attributes: List[AttributeWriteRequest], busyWaitMs: Union[None, int] = None) -> PyChipError: handle = GetLibraryHandle() @@ -1227,6 +1268,9 @@ def Init(): handle.pychip_WriteClient_WriteAttributes.restype = PyChipError handle.pychip_WriteClient_WriteGroupAttributes.restype = PyChipError + handle.pychip_WriteClient_TestOnlyWriteAttributesWithMismatchedTimedRequestField.restype = PyChipError + handle.pychip_WriteClient_TestOnlyWriteAttributesWithMismatchedTimedRequestField.argtypes = [py_object, c_void_p, + c_size_t, c_bool, c_size_t, c_size_t, POINTER(PyWriteAttributeData), c_size_t] # Both WriteAttributes and WriteGroupAttributes are variadic functions. As per ctype documentation # https://docs.python.org/3/library/ctypes.html#calling-varadic-functions, it is critical that we diff --git a/src/controller/python/matter/clusters/attribute.cpp b/src/controller/python/matter/clusters/attribute.cpp index 374a6c374a35ed..949603bf81585e 100644 --- a/src/controller/python/matter/clusters/attribute.cpp +++ b/src/controller/python/matter/clusters/attribute.cpp @@ -267,6 +267,10 @@ PyChipError pychip_WriteClient_WriteAttributes(void * appContext, DeviceProxy * size_t interactionTimeoutMsSizeT, size_t busyWaitMsSizeT, chip::python::PyWriteAttributeData * writeAttributesData, size_t attributeDataLength, bool forceLegacyListEncoding); +PyChipError pychip_WriteClient_TestOnlyWriteAttributesWithMismatchedTimedRequestField( + void * appContext, DeviceProxy * device, size_t timedWriteTimeoutMsSizeT, bool timedRequestFieldValue, + size_t interactionTimeoutMsSizeT, size_t busyWaitMsSizeT, chip::python::PyWriteAttributeData * writeAttributesData, + size_t attributeDataLength); PyChipError pychip_WriteClient_WriteGroupAttributes(size_t groupIdSizeT, chip::Controller::DeviceCommissioner * devCtrl, size_t busyWaitMsSizeT, chip::python::PyWriteAttributeData * writeAttributesData, @@ -318,6 +322,50 @@ class WriteClientCallback : public WriteClient::Callback using namespace chip::python; +namespace { +// Helper function to process write attributes data - reduces code duplication +CHIP_ERROR ProcessWriteAttributesData(WriteClient * client, python::PyWriteAttributeData * writeAttributesData, + size_t attributeDataLength, bool forceLegacyListEncoding = false) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + for (size_t i = 0; i < attributeDataLength; i++) + { + python::PyAttributePath path = writeAttributesData[i].attributePath; + void * tlv = writeAttributesData[i].tlvData; + size_t length = writeAttributesData[i].tlvLength; + + uint8_t * tlvBuffer = reinterpret_cast(tlv); + + TLV::TLVReader reader; + reader.Init(tlvBuffer, static_cast(length)); + reader.Next(); + Optional dataVersion; + if (path.hasDataVersion == 1) + { + dataVersion.SetValue(path.dataVersion); + } + + if (forceLegacyListEncoding) + { + auto listEncodingOverride = WriteClient::TestListEncodingOverride::kForceLegacyEncoding; + SuccessOrExit(err = client->PutPreencodedAttribute( + chip::app::ConcreteDataAttributePath(path.endpointId, path.clusterId, path.attributeId, dataVersion), + reader, listEncodingOverride)); + } + else + { + SuccessOrExit( + err = client->PutPreencodedAttribute( + chip::app::ConcreteDataAttributePath(path.endpointId, path.clusterId, path.attributeId, dataVersion), reader)); + } + } + +exit: + return err; +} +} // namespace + extern "C" { void pychip_WriteClient_InitCallbacks(OnWriteResponseCallback onWriteResponseCallback, OnWriteErrorCallback onWriteErrorCallback, OnWriteDoneCallback onWriteDoneCallback) @@ -364,31 +412,52 @@ PyChipError pychip_WriteClient_WriteAttributes(void * appContext, DeviceProxy * VerifyOrExit(device != nullptr && device->GetSecureSession().HasValue(), err = CHIP_ERROR_MISSING_SECURE_SESSION); - for (size_t i = 0; i < attributeDataLength; i++) + SuccessOrExit(err = + ProcessWriteAttributesData(client.get(), writeAttributesData, attributeDataLength, forceLegacyListEncoding)); + + SuccessOrExit(err = client->SendWriteRequest(device->GetSecureSession().Value(), + interactionTimeoutMs != 0 ? System::Clock::Milliseconds32(interactionTimeoutMs) + : System::Clock::kZero)); + + client.release(); + callback.release(); + + if (busyWaitMs) { - python::PyAttributePath path = writeAttributesData[i].attributePath; - void * tlv = writeAttributesData[i].tlvData; - size_t length = writeAttributesData[i].tlvLength; + usleep(busyWaitMs * 1000); + } - uint8_t * tlvBuffer = reinterpret_cast(tlv); +exit: + return ToPyChipError(err); +} - TLV::TLVReader reader; - reader.Init(tlvBuffer, static_cast(length)); - reader.Next(); - Optional dataVersion; - if (path.hasDataVersion == 1) - { - dataVersion.SetValue(path.dataVersion); - } +PyChipError pychip_WriteClient_TestOnlyWriteAttributesWithMismatchedTimedRequestField( + void * appContext, DeviceProxy * device, size_t timedWriteTimeoutMsSizeT, bool timedRequestFieldValue, + size_t interactionTimeoutMsSizeT, size_t busyWaitMsSizeT, python::PyWriteAttributeData * writeAttributesData, + size_t attributeDataLength) +{ + CHIP_ERROR err = CHIP_NO_ERROR; - auto listEncodingOverride = forceLegacyListEncoding ? WriteClient::TestListEncodingOverride::kForceLegacyEncoding - : WriteClient::TestListEncodingOverride::kNoOverride; + uint16_t timedWriteTimeoutMs = static_cast(timedWriteTimeoutMsSizeT); + uint16_t interactionTimeoutMs = static_cast(interactionTimeoutMsSizeT); + uint16_t busyWaitMs = static_cast(busyWaitMsSizeT); - SuccessOrExit(err = client->PutPreencodedAttribute( - chip::app::ConcreteDataAttributePath(path.endpointId, path.clusterId, path.attributeId, dataVersion), - reader, listEncodingOverride)); - } + std::unique_ptr callback = std::make_unique(appContext); + + // Use the TestOnly constructor that allows decoupling the Timed Request action from the TimedRequest field value. + // This allows testing mismatched scenarios: + // - timedWriteTimeoutMs = 0, timedRequestFieldValue = true: No action, but field=true (TIMED_REQUEST_MISMATCH) + // - timedWriteTimeoutMs > 0, timedRequestFieldValue = false: Action sent, but field=false (TIMED_REQUEST_MISMATCH) + std::unique_ptr client = std::make_unique( + app::InteractionModelEngine::GetInstance()->GetExchangeManager(), callback->GetChunkedCallback(), + timedWriteTimeoutMs != 0 ? Optional(timedWriteTimeoutMs) : Optional::Missing(), timedRequestFieldValue, + WriteClient::TestOnlyOverrideTimedRequestFieldTag{}); + + VerifyOrExit(device != nullptr && device->GetSecureSession().HasValue(), err = CHIP_ERROR_MISSING_SECURE_SESSION); + + SuccessOrExit(err = ProcessWriteAttributesData(client.get(), writeAttributesData, attributeDataLength)); + // Send WriteRequest - will trigger TIMED_REQUEST_MISMATCH if action and field don't match SuccessOrExit(err = client->SendWriteRequest(device->GetSecureSession().Value(), interactionTimeoutMs != 0 ? System::Clock::Milliseconds32(interactionTimeoutMs) : System::Clock::kZero)); diff --git a/src/python_testing/TC_IDM_3_2.py b/src/python_testing/TC_IDM_3_2.py new file mode 100644 index 00000000000000..cad931fde78b71 --- /dev/null +++ b/src/python_testing/TC_IDM_3_2.py @@ -0,0 +1,414 @@ +# +# Copyright (c) 2025 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: +# app: ${ALL_CLUSTERS_APP} +# app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json +# 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 +# factory-reset: true +# quiet: true +# === END CI TEST ARGUMENTS === + +import logging +from typing import Any, Optional + +from mobly import asserts + +import matter.clusters as Clusters +from matter.clusters import ClusterObjects as ClusterObjects +# from matter.exceptions import ChipStackError +from matter.interaction_model import InteractionModelError, Status +from matter.testing import global_attribute_ids +from matter.testing.basic_composition import BasicCompositionTests +from matter.testing.matter_testing import MatterBaseTest, TestStep, async_test_body, default_matter_test_main + + +class TC_IDM_3_2(MatterBaseTest, BasicCompositionTests): + """Test case for IDM-3.2: Write Response Action from DUT to TH. [{DUT_Server}]""" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.endpoint = 0 + + async def find_timed_write_attribute( + self, endpoints_data: dict[int, Any] + ) -> tuple[Optional[int], Optional[type[ClusterObjects.ClusterAttributeDescriptor]]]: + """ + Find an attribute that requires timed write on the actual device + Uses the wildcard read data that's already in endpoints_data + """ + logging.info(f"Searching for timed write attributes across {len(endpoints_data)} endpoints") + + for endpoint_id, endpoint in endpoints_data.items(): + for cluster_type, cluster_data in endpoint.items(): + cluster_id = cluster_type.id + + cluster_type_enum = global_attribute_ids.cluster_id_type(cluster_id) + # If debugging, please uncomment the following line to add Unit Testing clusters to the search and comment out the line below it. + if cluster_type_enum != global_attribute_ids.ClusterIdType.kStandard and cluster_type_enum != global_attribute_ids.ClusterIdType.kTest: + # if cluster_type_enum != global_attribute_ids.ClusterIdType.kStandard: + continue + + for attr_type in cluster_data: + # Check if this is an attribute descriptor class + if (isinstance(attr_type, type) and + issubclass(attr_type, ClusterObjects.ClusterAttributeDescriptor)): + # Check if this attribute requires timed write using the must_use_timed_write class property + if attr_type.must_use_timed_write: + logging.info(f"Found timed write attribute: {attr_type.__name__} " + f"in cluster {cluster_type.__name__} on endpoint {endpoint_id}") + return endpoint_id, attr_type + + logging.warning("No timed write attributes found on device") + return None, None + + def steps_TC_IDM_3_2(self) -> list[TestStep]: + steps = [ + TestStep(0, "Commissioning, already done", is_commissioning=True), + TestStep(1, "TH sends the WriteRequestMessage to the DUT to write any attribute on an unsupported Endpoint. DUT responds with the Write Response action", + "Verify on the TH that the DUT sends the status code UNSUPPORTED_ENDPOINT"), + TestStep(2, "TH sends the WriteRequestMessage to the DUT to write any attribute on an unsupported cluster. DUT responds with the Write Response action", + "Verify on the TH that the DUT sends the status code UNSUPPORTED_CLUSTER"), + TestStep(3, "TH sends the WriteRequestMessage to the DUT to write an unsupported attribute. DUT responds with the Write Response action", + "Verify on the TH that the DUT sends the status code UNSUPPORTED_ATTRIBUTE"), + TestStep(4, "TH sends the WriteRequestMessage to the DUT to modify the value of one attribute and Set SuppressResponse to True.", + "On the TH verify that the DUT does not send a Write Response message with a success back to the TH."), + TestStep(5, "TH sends a ReadRequest message to the DUT to read any attribute on any cluster. DUT returns with a report data action with the attribute values and the dataversion of the cluster. TH sends a WriteRequestMessage to the DUT to modify the value of one attribute with the DataVersion field set to the one received in the prior step.", + "Verify that the DUT sends a Write Response message with a success back to the TH. Verify by sending a ReadRequest that the Write Action on DUT was successful."), + TestStep(6, "TH sends a ReadRequest message to the DUT to read any attribute on any cluster. DUT returns with a report data action with the attribute values and the dataversion of the cluster. TH sends a WriteRequestMessage to the DUT to modify the value of one attribute no DataVersion indicated. TH sends a second WriteRequestMessage to the DUT to modify the value of an attribute with the dataversion field set to the value received earlier.", + "Verify that the DUT sends a Write Response message with the error DATA_VERSION_MISMATCH for the second Write request."), + TestStep(7, "TH sends the WriteRequestMessage to the DUT to modify the value of a specific attribute data that needs Timed Write transaction to write and this action is not part of a Timed Write transaction.", + "On the TH verify that the DUT sends a status code NEEDS_TIMED_INTERACTION."), + ] + return steps + + @async_test_body + async def test_TC_IDM_3_2(self): + self.step(0) + + # Test Setup with robust endpoint/cluster discovery + await self.setup_class_helper(allow_pase=False) + + self.step(1) + ''' + Write any attribute on an unsupported endpoint to DUT + Find an unsupported endpoint + ''' + supported_endpoints = set(self.endpoints.keys()) + all_endpoints = set(range(max(supported_endpoints) + 2)) + unsupported_endpoints = list(all_endpoints - supported_endpoints) + unsupported_endpoint = unsupported_endpoints[0] + + test_attribute = Clusters.Descriptor.Attributes.FeatureMap + + # Try to write to an unsupported endpoint using framework method + write_status = await self.write_single_attribute( + attribute_value=test_attribute(0), + endpoint_id=unsupported_endpoint, + expect_success=False + ) + + # Verify we get UNSUPPORTED_ENDPOINT error + asserts.assert_equal(write_status, Status.UnsupportedEndpoint, + f"Write to unsupported endpoint should return UNSUPPORTED_ENDPOINT, got {write_status}") + + self.step(2) + ''' + Write all attributes on an unsupported cluster to DUT + Find an unsupported cluster + ''' + supported_cluster_ids = set() + for endpoint_clusters in self.endpoints.values(): + supported_cluster_ids.update({cluster.id for cluster in endpoint_clusters.keys( + ) if global_attribute_ids.cluster_id_type(cluster.id) == global_attribute_ids.ClusterIdType.kStandard}) + + # Get all possible standard clusters + all_standard_cluster_ids = {cluster_id for cluster_id in ClusterObjects.ALL_CLUSTERS.keys( + ) if global_attribute_ids.cluster_id_type(cluster_id) == global_attribute_ids.ClusterIdType.kStandard} + + # Find unsupported clusters + unsupported_cluster_ids = all_standard_cluster_ids - supported_cluster_ids + + if not unsupported_cluster_ids: + self.skip_step("No unsupported standard clusters found to test") + + # Use the first unsupported cluster + unsupported_cluster_id = next(iter(unsupported_cluster_ids)) + cluster_attributes = ClusterObjects.ALL_ATTRIBUTES[unsupported_cluster_id] + test_unsupported_attribute = next(iter(cluster_attributes.values())) + + write_status = await self.write_single_attribute( + attribute_value=test_unsupported_attribute, + endpoint_id=self.endpoint, + expect_success=False + ) + # Verify we get UNSUPPORTED_CLUSTER error + asserts.assert_equal(write_status, Status.UnsupportedCluster, + f"Write to unsupported cluster should return UNSUPPORTED_CLUSTER, got {write_status}") + + self.step(3) + # Write an unsupported attribute to DUT + unsupported_endpoint = None + unsupported_attribute = None + for endpoint_id, endpoint in self.endpoints.items(): + for cluster_type, cluster_data in endpoint.items(): + if global_attribute_ids.cluster_id_type(cluster_type.id) != global_attribute_ids.ClusterIdType.kStandard: + continue + + all_attrs = set(ClusterObjects.ALL_ATTRIBUTES[cluster_type.id].keys()) + dut_attrs = set(cluster_data.get(cluster_type.Attributes.AttributeList, [])) + + unsupported = [ + attr_id for attr_id in (all_attrs - dut_attrs) + if global_attribute_ids.attribute_id_type(attr_id) == global_attribute_ids.AttributeIdType.kStandardNonGlobal + ] + if unsupported: + unsupported_attribute = ClusterObjects.ALL_ATTRIBUTES[cluster_type.id][unsupported[0]] + unsupported_endpoint = endpoint_id + logging.info(f"Found unsupported attribute: {unsupported_attribute} in cluster {cluster_type.id}") + break + if unsupported_attribute: + logging.info(f"Unsupported attribute: {unsupported_attribute}") + break + + if not unsupported_attribute: + logging.warning("No unsupported attributes found - this may be OK for non-commissionable devices") + else: + write_status2 = await self.write_single_attribute( + attribute_value=unsupported_attribute(0), + endpoint_id=unsupported_endpoint, + expect_success=False + ) + logging.info(f"Writing unsupported attribute: {unsupported_attribute}") + asserts.assert_equal(write_status2, Status.UnsupportedAttribute, + f"Write to unsupported attribute should return UNSUPPORTED_ATTRIBUTE, got {write_status2}") + + self.skip_step(4) + # Currently skipping step 4 as we have removed support in the python framework for this functionality currently. + # This is now contained in the SuppressResponse test module PR referenced below for TC_IDM_3_2, once this test module merges that PR can then be merged + # and this test step will become valid after issues with SuppressResponse mentioned in issue https://github.com/project-chip/connectedhomeip/issues/41227. + # SuppressResponse PR Reference: https://github.com/project-chip/connectedhomeip/pull/41590 + # TODO: Once the SuppressResponse test module PR merges, uncomment the following code and remove the skip_step line above. + + """ + self.step(4) + # Check if NodeLabel attribute exists for step 4 (SuppressResponse tests) + if await self.attribute_guard(endpoint=self.endpoint, attribute=Clusters.BasicInformation.Attributes.NodeLabel): + ''' + TH sends the WriteRequestMessage to the DUT to modify the value of one attribute and Set SuppressResponse to True. + + NOTE: Per Issue #41227, the current spec does not strictly enforce that devices must suppress the response. + For now, we just ensure the device doesn't crash. The device MAY respond or may not - either is acceptable. + Future spec revisions will enforce no response behavior. + Reference: https://github.com/project-chip/connectedhomeip/issues/41227 + ''' + + test_attribute = Clusters.BasicInformation.Attributes.NodeLabel + test_value = "SuppressResponse-Test" + + logging.info("Testing SuppressResponse functionality with NodeLabel attribute") + logging.info("NOTE: Device may or may not respond - both behaviors are acceptable for now per Issue #41227") + + # Send write request with suppressResponse=True + # Device may respond or not - we just ensure it doesn't crash + try: + res = await self.default_controller.WriteAttribute( + nodeid=self.dut_node_id, + attributes=[(self.endpoint, test_attribute(test_value))], + suppressResponse=True + ) + logging.info(f"Device responded to suppressResponse=True request: {res}") + except Exception as e: + # Device didn't respond (timeout or other error) - this is also acceptable + logging.info(f"Device did not respond or encountered error: {e}") + + # Verify the write operation succeeded by reading back the value + logging.info("Verifying that the write operation succeeded") + actual_value = await self.read_single_attribute_check_success( + endpoint=self.endpoint, + cluster=Clusters.BasicInformation, + attribute=test_attribute + ) + + asserts.assert_equal(actual_value, test_value, + f"Attribute should be written. Expected {test_value}, got {actual_value}") + """ + # Check if NodeLabel attribute exists for steps 5 and 6 (DataVersion test steps) + self.step(5) + if await self.attribute_guard(endpoint=self.endpoint, attribute=Clusters.BasicInformation.Attributes.NodeLabel): + ''' + TH sends a ReadRequest message to the DUT to read any attribute on any cluster. + DUT returns with a report data action with the attribute values and the dataversion of the cluster. + TH sends a WriteRequestMessage to the DUT to modify the value of one attribute with the DataVersion field set to the one received in the prior step. + ''' + test_cluster = Clusters.BasicInformation + test_attribute = Clusters.BasicInformation.Attributes.NodeLabel + new_value0 = "New-Label-Step5" + + # Read an attribute to get the current DataVersion + read_result = await self.default_controller.ReadAttribute( + self.dut_node_id, + [(self.endpoint, test_cluster, test_attribute)] + ) + + # Get the current DataVersion + current_data_version = read_result[self.endpoint][test_cluster][Clusters.Attribute.DataVersion] + logging.info(f"Current DataVersion for cluster {test_cluster.id}: {current_data_version}") + + write_result = await self.default_controller.WriteAttribute( + self.dut_node_id, + [(self.endpoint, test_attribute(new_value0), current_data_version)] + ) + + # Verify write was successful + asserts.assert_equal(write_result[0].Status, Status.Success, + f"Write with correct DataVersion should succeed, got {write_result[0].Status}") + + # Verify the value was written by reading it back + actual_value = await self.read_single_attribute_check_success(endpoint=self.endpoint, cluster=test_cluster, attribute=test_attribute) + + asserts.assert_equal(actual_value, new_value0, + f"Read value {actual_value} should match written value {new_value0}") + + self.step(6) + if await self.attribute_guard(endpoint=self.endpoint, attribute=Clusters.BasicInformation.Attributes.NodeLabel): + ''' + TH sends a ReadRequest message to the DUT to read any attribute on any cluster. + DUT returns with a report data action with the attribute values and the dataversion of the cluster. + TH sends a WriteRequestMessage to the DUT to modify the value of one attribute no DataVersion indicated. + TH sends a second WriteRequestMessage to the DUT to modify the value of an attribute with the dataversion field set to the value received earlier. + ''' + + # First, read to get the initial DataVersion + initial_read = await self.default_controller.ReadAttribute( + self.dut_node_id, + [(self.endpoint, test_cluster, test_attribute)] + ) + + initial_data_version = initial_read[self.endpoint][test_cluster][Clusters.Attribute.DataVersion] + logging.info(f"Initial DataVersion for step 6: {initial_data_version}") + + # Write without DataVersion (this should succeed and increment the DataVersion) + new_value1 = "New-Label-Step6" + write_status = await self.write_single_attribute( + attribute_value=test_attribute(new_value1), + endpoint_id=self.endpoint, + expect_success=True + ) + + # Now try to write with the old DataVersion (this should fail with DATA_VERSION_MISMATCH) + new_value2 = "New-Label-Step6-2" + write_result_old_version = await self.default_controller.WriteAttribute( + self.dut_node_id, + [(self.endpoint, test_attribute(new_value2), initial_data_version)] + ) + + # Verify we get DATA_VERSION_MISMATCH error + asserts.assert_equal(write_result_old_version[0].Status, Status.DataVersionMismatch, + f"Write with old DataVersion should return DATA_VERSION_MISMATCH, got {write_result_old_version[0].Status}") + + else: + # NodeLabel doesn't exist - skip these steps for now + # Created following follow-up task for the event that the node label attribute does not exist + # TODO: https://github.com/project-chip/matter-test-scripts/issues/693 + logging.info("NodeLabel not found - this may be a non-commissionable device") + + endpoint_id, timed_attr = await self.find_timed_write_attribute(self.endpoints) + if timed_attr: + self.step(7) + ''' + TH sends the WriteRequestMessage to the DUT to modify the value of a specific attribute data that needs + timed write transaction to write and this action is not part of a timed write transaction. + + This step tests the following 3 timed write error scenarios: + 1. NEEDS_TIMED_INTERACTION: Writing timed-write-required attribute without timed transaction + 2. TIMED_REQUEST_MISMATCH: Writing with TimedRequest flag but no actual timed transaction + (Timed Request ACTION = No, TimedRequest FLAG = True) + 3. TIMED_REQUEST_MISMATCH: Writing with timed action performed but TimedRequest flag set to false + (Timed Request ACTION = Yes, TimedRequest FLAG = False) + + Understanding the distinction: + - TIMED REQUEST ACTION: The TimedRequest protocol message sent BEFORE the WriteRequest + - TIMEDREQUEST FLAG: A boolean field IN the WriteRequest message itself + + Normal timed write: Action=Yes, Flag=True (both must match) + ''' + + # Test with the real timed-write attribute found on the device + logging.info(f"Testing timed write attribute: {timed_attr}") + + # Test NEEDS_TIMED_INTERACTION - Writing timed-write-required attribute without timed transaction + # Found below logic in /home/ubuntu/connectedhomeapi/connectedhomeip/src/controller/python/tests/scripts/cluster_objects.py and TC_IDM_1_2 test logic. + logging.info("Writing timed-write-required attribute without timedRequestTimeoutMs should be rejected") + try: + await self.default_controller.WriteAttribute( + self.dut_node_id, + attributes=[(endpoint_id, timed_attr(True))] + ) + asserts.fail("The write request should be rejected due to InteractionModelError: NeedsTimedInteraction (0xc6).") + except InteractionModelError as e: + asserts.assert_equal(e.status, Status.NeedsTimedInteraction, + f"WriteAttribute should return NeedsTimedInteraction, got {e.status}") + + # TIMED_REQUEST_MISMATCH - Writing with TimedRequest flag but no actual timed transaction + # Thanks to Cecille for the guidance on the test step logic and plumbing for this to function below. + logging.info("Writing with TimedRequest flag but no timed transaction should return TIMED_REQUEST_MISMATCH") + try: + await self.default_controller.TestOnlyWriteAttributeWithMismatchedTimedRequestField( + self.dut_node_id, + timedRequestTimeoutMs=0, # No timed action + timedRequestFieldValue=True, # But field=true + attributes=[(endpoint_id, timed_attr(False))] + ) + asserts.fail("The write request should be rejected due to InteractionModelError: TimedRequestMismatch (0xc9).") + except InteractionModelError as e: + asserts.assert_equal(e.status, Status.TimedRequestMismatch, + f"WriteAttribute should return TimedRequestMismatch, got {e.status}") + + # TIMED_REQUEST_MISMATCH - Writing with timed action performed but TimedRequest flag set to false + logging.info("Writing with timed action but TimedRequest flag=false should return TIMED_REQUEST_MISMATCH") + try: + await self.default_controller.TestOnlyWriteAttributeWithMismatchedTimedRequestField( + self.dut_node_id, + timedRequestTimeoutMs=1000, # Timed action performed + timedRequestFieldValue=False, # But field=false + attributes=[(endpoint_id, timed_attr(False))] + ) + asserts.fail("The write request should be rejected due to InteractionModelError: TimedRequestMismatch (0xc9).") + except InteractionModelError as e: + asserts.assert_equal(e.status, Status.TimedRequestMismatch, + f"WriteAttribute should return TimedRequestMismatch, got {e.status}") + + else: + self.skip_step(7) + + +if __name__ == "__main__": + default_matter_test_main()