Skip to content
Open
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
67f7371
[Create Test] Creating IDM_3_2 python3 test module
j-ororke Sep 20, 2025
8f46e31
Update src/python_testing/TC_IDM_3_2.py
j-ororke Sep 20, 2025
42e50eb
Update src/python_testing/TC_IDM_3_2.py
j-ororke Sep 20, 2025
4c4a4b5
Restyled by autopep8
restyled-commits Sep 20, 2025
6c3d0aa
Removing unneeded imports
j-ororke Sep 20, 2025
669a2a7
Resolving linting errors
j-ororke Sep 20, 2025
d072b90
Restyled by autopep8
restyled-commits Sep 20, 2025
3ddd434
Resolving linting errors part 2
j-ororke Sep 20, 2025
605c6cc
Updating docstrings to be more logical and understandable
j-ororke Sep 20, 2025
dcd0548
Updating docstrings further for clarity
j-ororke Sep 20, 2025
be9e579
Adding plumbing for TIMED_REQUEST_MISMATCH validation, changed to nod…
j-ororke Sep 23, 2025
b3d8e2d
Restyled by whitespace
restyled-commits Sep 23, 2025
bc1626f
Restyled by clang-format
restyled-commits Sep 23, 2025
8439c8a
Restyled by autopep8
restyled-commits Sep 23, 2025
7210fae
Update TC_IDM_3_2.py
j-ororke Sep 23, 2025
55716ed
Resolving linting errors and added some additional comments to test s…
j-ororke Sep 23, 2025
282ebdb
Resolving code dups in ChipDeviceCtrl.py, Attribute.py, and attribute…
j-ororke Sep 23, 2025
f688582
Restyled by whitespace
restyled-commits Sep 23, 2025
9a7ca6f
Restyled by clang-format
restyled-commits Sep 23, 2025
d1f78ea
Restyled by autopep8
restyled-commits Sep 23, 2025
d095563
Merge branch 'master' into create_TC_IDM_3_2_python3_test
j-ororke Sep 24, 2025
cb26e88
Merge branch 'master' into create_TC_IDM_3_2_python3_test
j-ororke Sep 25, 2025
b4e13fb
Refactored timed request logic and improve attribute discovery
j-ororke Sep 26, 2025
c292e5c
Restyled by autopep8
restyled-commits Sep 26, 2025
474e423
Implement SuppressResponse functionality and fix WriteHandler bug per…
j-ororke Sep 29, 2025
30de357
Restyled by whitespace
restyled-commits Sep 29, 2025
6786a1b
Restyled by clang-format
restyled-commits Sep 29, 2025
ec8efb6
Restyled by autopep8
restyled-commits Sep 29, 2025
1de2422
Restyled by isort
restyled-commits Sep 29, 2025
92278a2
Update WriteHandler.cpp
j-ororke Oct 2, 2025
d0d7e7b
Update TC_IDM_3_2.py
j-ororke Oct 2, 2025
f3bc13c
Update TC_IDM_3_2.py
j-ororke Oct 2, 2025
1dcc960
Update TC_IDM_3_2.py
j-ororke Oct 2, 2025
2a482dc
Applying autopep8
j-ororke Oct 2, 2025
8c63503
Recovering lost variable
j-ororke Oct 2, 2025
1c4bf3a
Restyled by autopep8
restyled-commits Oct 2, 2025
0dbbb1f
resolving lint error
j-ororke Oct 2, 2025
702e0fd
Restyled by autopep8
restyled-commits Oct 2, 2025
a9fa14c
Update src/controller/python/matter/ChipDeviceCtrl.py
j-ororke Oct 7, 2025
d80c2c5
Address PR feedback from Cecille: improve type safety and simplify at…
j-ororke Oct 8, 2025
80c4bf4
Restyled by autopep8
restyled-commits Oct 8, 2025
f4416a2
Resolving linting error and removing some unneeded comments
j-ororke Oct 8, 2025
4a14bc9
Merge branch 'master' into create_TC_IDM_3_2_python3_test
j-ororke Oct 9, 2025
6189f8a
Merge branch 'master' into create_TC_IDM_3_2_python3_test
j-ororke Oct 9, 2025
ad4c031
Merge branch 'master' into create_TC_IDM_3_2_python3_test
j-ororke Oct 10, 2025
6a364bc
Merge branch 'master' into create_TC_IDM_3_2_python3_test
j-ororke Oct 16, 2025
d264c6e
Apply suggestions from code review by Cecille
j-ororke Oct 17, 2025
b6eb409
Improve TC_IDM_3_2 and add type hint to Attribute.py's _prepare_write…
j-ororke Oct 18, 2025
6eba285
Restyled by autopep8
restyled-commits Oct 18, 2025
bde6d01
Improve timed write documentation and add missing edge case test scen…
j-ororke Oct 22, 2025
13ea73f
Restyled by whitespace
restyled-commits Oct 22, 2025
3ae33ff
Restyled by clang-format
restyled-commits Oct 22, 2025
79fc25b
Restyled by autopep8
restyled-commits Oct 22, 2025
4b20b2e
Merge branch 'master' into create_TC_IDM_3_2_python3_test
j-ororke Oct 23, 2025
4c5ecf3
Apply suggestions for timed request field value name change from Boris
j-ororke Oct 23, 2025
3c0d450
Rename mForceTimedRequestFlag to mTimedRequestFieldValue and improve …
j-ororke Oct 23, 2025
e4292a7
Removed SuppressResponse enablement from current python framework cha…
j-ororke Oct 23, 2025
7ea0969
Correcting test step 4 comment to provide issue link and more context
j-ororke Oct 23, 2025
d9b4f30
Restyled by whitespace
restyled-commits Oct 23, 2025
a9356fc
Restyled by clang-format
restyled-commits Oct 23, 2025
49c1782
Restyled by autopep8
restyled-commits Oct 23, 2025
b896bb9
Remove leftover SuppressResponse reference and revert unintended file…
j-ororke Oct 23, 2025
610043b
Consolidate redundant test-only Timed Request code per Boris's feedback:
j-ororke Oct 23, 2025
48b1ff7
Restyled by clang-format
restyled-commits Oct 23, 2025
b9463af
Restyled by autopep8
restyled-commits Oct 23, 2025
7eeb60c
Merge branch 'master' into create_TC_IDM_3_2_python3_test
j-ororke Oct 24, 2025
200bd11
Merge branch 'master' into create_TC_IDM_3_2_python3_test
j-ororke Nov 6, 2025
be4547d
Refactor attribute write helpers per code review from Cecille:
j-ororke Nov 6, 2025
1210325
Resolving minor noticed change aftter merging master
j-ororke Nov 6, 2025
431a12d
Restyled by clang-format
restyled-commits Nov 6, 2025
2c38877
Restyled by autopep8
restyled-commits Nov 6, 2025
f3f405e
Resolving linting errors
j-ororke Nov 6, 2025
c13545d
Restyled by autopep8
restyled-commits Nov 6, 2025
dc88770
Resolving mypy issues
j-ororke Nov 6, 2025
d10727e
Restyled by autopep8
restyled-commits Nov 6, 2025
7d3f430
Resolving further mypy issues in ChipDeviceCtrl python3 module
j-ororke Nov 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/app/WriteClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ CHIP_ERROR WriteClient::StartNewMessage()

ReturnErrorOnFailure(mWriteRequestBuilder.Init(&mMessageWriter));
mWriteRequestBuilder.SuppressResponse(mSuppressResponse);
mWriteRequestBuilder.TimedRequest(mTimedWriteTimeoutMs.HasValue());
mWriteRequestBuilder.TimedRequest(mTimedRequest);
ReturnErrorOnFailure(mWriteRequestBuilder.GetError());
mWriteRequestBuilder.CreateWriteRequests();
ReturnErrorOnFailure(mWriteRequestBuilder.GetError());
Expand Down
13 changes: 11 additions & 2 deletions src/app/WriteClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ class WriteClient : public Messaging::ExchangeDelegate
bool aSuppressResponse = false) :
mpExchangeMgr(apExchangeMgr),
mExchangeCtx(*this), mpCallback(apCallback), mTimedWriteTimeoutMs(aTimedWriteTimeoutMs),
mSuppressResponse(aSuppressResponse)
mSuppressResponse(aSuppressResponse), mTimedRequest(aTimedWriteTimeoutMs.HasValue())
{
assertChipStackLockedByCurrentThread();
}
Expand All @@ -138,7 +138,15 @@ class WriteClient : public Messaging::ExchangeDelegate
WriteClient(Messaging::ExchangeManager * apExchangeMgr, Callback * apCallback, const Optional<uint16_t> & aTimedWriteTimeoutMs,
uint16_t aReservedSize) :
mpExchangeMgr(apExchangeMgr),
mExchangeCtx(*this), mpCallback(apCallback), mTimedWriteTimeoutMs(aTimedWriteTimeoutMs), mReservedSize(aReservedSize)
mExchangeCtx(*this), mpCallback(apCallback), mTimedWriteTimeoutMs(aTimedWriteTimeoutMs), mReservedSize(aReservedSize),
mTimedRequest(aTimedWriteTimeoutMs.HasValue())
{
assertChipStackLockedByCurrentThread();
}

// TestOnly constructor that allows setting TimedRequest flag without timeout
WriteClient(Messaging::ExchangeManager * apExchangeMgr, Callback * apCallback, bool aTimedRequest) :
mpExchangeMgr(apExchangeMgr), mExchangeCtx(*this), mpCallback(apCallback), mTimedRequest(aTimedRequest)
{
assertChipStackLockedByCurrentThread();
}
Expand Down Expand Up @@ -524,6 +532,7 @@ class WriteClient : public Messaging::ExchangeDelegate
// #if CONFIG_BUILD_FOR_HOST_UNIT_TEST
uint16_t mReservedSize = 0;
// #endif
bool mTimedRequest = false;

/**
* Below we define several const variables for encoding overheads.
Expand Down
398 changes: 0 additions & 398 deletions src/app/tests/suites/certification/Test_TC_IDM_3_2.yaml

This file was deleted.

1 change: 0 additions & 1 deletion src/app/tests/suites/manualTests.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,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",
Expand Down
51 changes: 43 additions & 8 deletions src/controller/python/matter/ChipDeviceCtrl.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -1582,6 +1582,48 @@ async def TestOnlySendCommandTimedRequestFlagWithNoTimedInvoke(self, nodeid: int
), payload).raise_on_error()
return await future

def _prepare_write_attribute_requests(self, attributes: typing.List[typing.Tuple[int, ClusterObjects.ClusterAttributeDescriptor]]) -> typing.List[ClusterAttribute.AttributeWriteRequest]:
"""Helper method to prepare attribute write requests."""
attrs = []
for v in attributes:
if len(v) == 2:
attrs.append(ClusterAttribute.AttributeWriteRequest(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would you mind adding the parameter names here so it's easier to tell what's going on? Is the attribute parameter list supposed to be a union of possible tuples?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added the parameter names to the AttributeWriteRequest calls and updated the type hint to show it accepts either a 2-tuple or 3-tuple.
A bit clearer now. Thank you!

v[0], v[1], 0, 0, v[1].value)) # type: ignore[attr-defined]
else:
attrs.append(ClusterAttribute.AttributeWriteRequest(
v[0], v[1], v[2], 1, v[1].value))
return attrs

async def TestOnlyWriteAttributeTimedRequestFlagWithNoTimedAction(self, nodeid: int,
attributes: typing.List[typing.Tuple[int, ClusterObjects.ClusterAttributeDescriptor]],
interactionTimeoutMs: typing.Optional[int] = None, busyWaitMs: typing.Optional[int] = None,
payloadCapability: int = TransportPayloadCapability.MRP_PAYLOAD):
'''
ONLY TO BE USED FOR TEST: Write attributes with TimedRequest flag but no TimedAction transaction.
This should result in TIMED_REQUEST_MISMATCH error.

Please see WriteAttribute for description of parameters.

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.TestOnlyWriteAttributeTimedRequestFlagWithNoTimedAction(
future, eventLoop, device.deviceProxy, attrs,
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,
Expand Down Expand Up @@ -1727,14 +1769,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,
Expand Down
73 changes: 55 additions & 18 deletions src/controller/python/matter/clusters/Attribute.py
100644 → 100755
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not check all the FFI bits in here carefully.... I am assuming @cecille will or has.

Original file line number Diff line number Diff line change
Expand Up @@ -1010,35 +1010,38 @@ 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):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please add the return here in the type hint as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! I've added the return type hint -> Tuple[ctypes.Array[PyWriteAttributeData], int] to the _prepare_write_attributes_data function. It returns a tuple containing the ctypes array and the count of attributes.

"""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(
Expand All @@ -1057,6 +1060,37 @@ def WriteAttributes(future: Future, eventLoop, device,
return res


def TestOnlyWriteAttributeTimedRequestFlagWithNoTimedAction(future: Future, eventLoop, device,
attributes: List[AttributeWriteRequest],
interactionTimeoutMs: Union[None, int] = None,
busyWaitMs: Union[None, int] = None) -> PyChipError:
'''
ONLY TO BE USED FOR TEST: Writes attributes with TimedRequest flag but no TimedAction transaction
This should result in TIMED_REQUEST_MISMATCH error.
'''
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 sets TimedRequest=True but no timed transaction
res = builtins.chipStack.Call(
lambda: handle.pychip_WriteClient_TestOnlyWriteAttributesTimedRequestNoTimedAction(
ctypes.py_object(transaction), device,
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()

Expand Down Expand Up @@ -1227,6 +1261,9 @@ def Init():

handle.pychip_WriteClient_WriteAttributes.restype = PyChipError
handle.pychip_WriteClient_WriteGroupAttributes.restype = PyChipError
handle.pychip_WriteClient_TestOnlyWriteAttributesTimedRequestNoTimedAction.restype = PyChipError
handle.pychip_WriteClient_TestOnlyWriteAttributesTimedRequestNoTimedAction.argtypes = [py_object, c_void_p,
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
Expand Down
120 changes: 103 additions & 17 deletions src/controller/python/matter/clusters/attribute.cpp
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Glanced over this, and seems ok, but did not review carefully.

Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,9 @@ 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_TestOnlyWriteAttributesTimedRequestNoTimedAction(
void * appContext, DeviceProxy * device, 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,
Expand Down Expand Up @@ -318,6 +321,40 @@ 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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the difference between the processing in this function vs. the legacy list encoding?

Copy link
Contributor Author

@j-ororke j-ororke Sep 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Cecille,

To answer your question about the difference between the processing in this helper function vs. the legacy list encoding:
The key difference is the listEncodingOverride parameter. When fixing the code duplication suggested by the Gemini AI bot, I was very careful not to touch Amine's legacy list encoding work done.

Here's what was preserved:

  • Legacy path (forceLegacyListEncoding = true): Still uses the original dedicated loop with WriteClient::TestListEncodingOverride::kForceLegacyEncoding parameter - completely unchanged from Amine's implementation

  • Normal path (forceLegacyListEncoding = false): Now uses the new ProcessWriteAttributesData() helper function, which calls PutPreencodedAttribute() without the listEncodingOverride parameter (defaults to kNoOverride)

The helper function intentionally does not include the listEncodingOverride parameter because:

  • It's only used for the normal (non-legacy) case
  • This keeps the legacy logic completely separate and untouched
  • The TestOnly function also uses normal encoding (no legacy override needed)

So Amine's legacy list encoding functionality is 100% preserved - we just extracted the common attribute processing logic that was duplicated between the two WriteClient functions while keeping the legacy-specific code path intact.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, but the code is the same, right? So the code below is doing

if (forceLegacy) {
   // do exactly this, but with one additional argument to PutPreencodedAttribute
} else {
   // do exactly this
}

So why not just have the forcelegacy section also use this function and just pass in the required parameter?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood, that is sorta what I had initially, then when I submitted the change and requested a re-review from the Gemini AI bot it suggested doing what I did above here in its comment on the change.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that comment, the bot suggests including forceLegacyListEncoding as a parameter. But you have something different than what the bot suggested regardless.

CHIP_ERROR FinishWriteAttributes(std::unique_ptr<WriteClient> client, std::unique_ptr<WriteClientCallback> callback,
                                 DeviceProxy * device, uint16_t interactionTimeoutMs, uint16_t busyWaitMs,
                                 python::PyWriteAttributeData * writeAttributesData, size_t attributeDataLength,
                                 bool forceLegacyListEncoding)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Cecille, you are correct.
Looking back at the bot's comment, I see now that I misinterpreted what it was suggesting. I thought it was recommending to keep the paths separate, but re-reading it, I can see it was suggesting to add forceLegacyListEncoding as a parameter.
When I was implementing it, I was being cautious about not refactoring Amine's legacy encoding code that's already validated through our ACL tests. So I ended up taking the more conservative route of only extracting the helper for the new paths.
If you'd prefer I refactor it to match what the bot actually suggested - adding the forceLegacyListEncoding parameter so all paths use the helper function - I'm happy to do that. Just let me know!
Thanks for catching this!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, if you can get it so the legacy list encoding just calls this function with a parameter, that would be cleaner.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it!
I've refactored the C++ code here.
The ProcessWriteAttributesData helper function now accepts a forceLegacyListEncoding parameter (defaulting to false), and handles both encoding paths.
The pychip_WriteClient_WriteAttributes function now just calls the helper with the appropriate flag instead of duplicating the logic.
Thank you!

size_t attributeDataLength)
{
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<uint8_t *>(tlv);

TLV::TLVReader reader;
reader.Init(tlvBuffer, static_cast<uint32_t>(length));
reader.Next();
Optional<DataVersion> dataVersion;
if (path.hasDataVersion == 1)
{
dataVersion.SetValue(path.dataVersion);
}

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)
Expand Down Expand Up @@ -364,31 +401,80 @@ 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++)
// Handle legacy list encoding override if needed
if (forceLegacyListEncoding)
{
python::PyAttributePath path = writeAttributesData[i].attributePath;
void * tlv = writeAttributesData[i].tlvData;
size_t length = writeAttributesData[i].tlvLength;
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<uint8_t *>(tlv);
uint8_t * tlvBuffer = reinterpret_cast<uint8_t *>(tlv);

TLV::TLVReader reader;
reader.Init(tlvBuffer, static_cast<uint32_t>(length));
reader.Next();
Optional<DataVersion> dataVersion;
if (path.hasDataVersion == 1)
{
dataVersion.SetValue(path.dataVersion);
TLV::TLVReader reader;
reader.Init(tlvBuffer, static_cast<uint32_t>(length));
reader.Next();
Optional<DataVersion> dataVersion;
if (path.hasDataVersion == 1)
{
dataVersion.SetValue(path.dataVersion);
}

auto listEncodingOverride = WriteClient::TestListEncodingOverride::kForceLegacyEncoding;

SuccessOrExit(err = client->PutPreencodedAttribute(
chip::app::ConcreteDataAttributePath(path.endpointId, path.clusterId, path.attributeId, dataVersion),
reader, listEncodingOverride));
}
}
else
{
SuccessOrExit(err = ProcessWriteAttributesData(client.get(), writeAttributesData, attributeDataLength));
}

auto listEncodingOverride = forceLegacyListEncoding ? WriteClient::TestListEncodingOverride::kForceLegacyEncoding
: WriteClient::TestListEncodingOverride::kNoOverride;
SuccessOrExit(err = client->SendWriteRequest(device->GetSecureSession().Value(),
interactionTimeoutMs != 0 ? System::Clock::Milliseconds32(interactionTimeoutMs)
: System::Clock::kZero));

SuccessOrExit(err = client->PutPreencodedAttribute(
chip::app::ConcreteDataAttributePath(path.endpointId, path.clusterId, path.attributeId, dataVersion),
reader, listEncodingOverride));
client.release();
callback.release();

if (busyWaitMs)
{
usleep(busyWaitMs * 1000);
}

exit:
return ToPyChipError(err);
}

PyChipError pychip_WriteClient_TestOnlyWriteAttributesTimedRequestNoTimedAction(void * appContext, DeviceProxy * device,
size_t interactionTimeoutMsSizeT,
size_t busyWaitMsSizeT,
python::PyWriteAttributeData * writeAttributesData,
size_t attributeDataLength)
{
CHIP_ERROR err = CHIP_NO_ERROR;

uint16_t interactionTimeoutMs = static_cast<uint16_t>(interactionTimeoutMsSizeT);
uint16_t busyWaitMs = static_cast<uint16_t>(busyWaitMsSizeT);

std::unique_ptr<WriteClientCallback> callback = std::make_unique<WriteClientCallback>(appContext);

// CRITICAL: Use TestOnly constructor to set TimedRequest flag without timeout.
// This function intentionally sets TimedRequest=true but does NOT send a TimedRequest action first.
// This should result in TIMED_REQUEST_MISMATCH error.
std::unique_ptr<WriteClient> client = std::make_unique<WriteClient>(
app::InteractionModelEngine::GetInstance()->GetExchangeManager(), callback->GetChunkedCallback(),
true); // Set TimedRequest flag to true without timeout

VerifyOrExit(device != nullptr && device->GetSecureSession().HasValue(), err = CHIP_ERROR_MISSING_SECURE_SESSION);

SuccessOrExit(err = ProcessWriteAttributesData(client.get(), writeAttributesData, attributeDataLength));

// Send WriteRequest with TimedRequest flag set but no preceding TimedRequest action
// This should trigger TIMED_REQUEST_MISMATCH error
SuccessOrExit(err = client->SendWriteRequest(device->GetSecureSession().Value(),
interactionTimeoutMs != 0 ? System::Clock::Milliseconds32(interactionTimeoutMs)
: System::Clock::kZero));
Expand Down
Loading
Loading