From c0c925c04ac2f92cd06eb83e9cb194114b16861e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Colomb?= Date: Wed, 11 Jun 2025 11:24:09 +0200 Subject: [PATCH] Abort SDO server upload when the object has zero length. A data size of zero bytes cannot be encoded in the "number of bytes that do not contain data" semantics, as that would require n=4, which is however limited to two bits (maximum value 3). So the SDO expedited upload protocol simply cannot convey that condition. The current implementation however still tries and thereby corrupts the server command specifier field value. Avoid that protocol violation by responding with an SDO abort code of 0800 0024h, No data available. Add a test case for this condition, verifying that the client raises the appropriate SdoAbortedError exception. --- canopen/sdo/server.py | 6 +++++- test/test_local.py | 7 +++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/canopen/sdo/server.py b/canopen/sdo/server.py index c6a4c27c..a34b3e68 100644 --- a/canopen/sdo/server.py +++ b/canopen/sdo/server.py @@ -66,7 +66,11 @@ def init_upload(self, request): data = self._node.get_data(index, subindex, check_readable=True) size = len(data) - if size <= 4: + if size == 0: + logger.info("No content to upload for 0x%04X:%02X", index, subindex) + self.abort(0x0800_0024) + return + elif size <= 4: logger.info("Expedited upload for 0x%04X:%02X", index, subindex) res_command |= EXPEDITED res_command |= (4 - size) << 2 diff --git a/test/test_local.py b/test/test_local.py index e184c040..31404bd6 100644 --- a/test/test_local.py +++ b/test/test_local.py @@ -62,6 +62,13 @@ def test_expedited_upload_default_value_real(self): sampling_rate = self.remote_node.sdo["Sensor Sampling Rate (Hz)"].raw self.assertAlmostEqual(sampling_rate, 5.2, places=2) + def test_upload_zero_length(self): + self.local_node.sdo["Manufacturer device name"].raw = b"" + with self.assertRaises(canopen.SdoAbortedError) as error: + self.remote_node.sdo["Manufacturer device name"].data + # Should be No data available + self.assertEqual(error.exception.code, 0x0800_0024) + def test_segmented_upload(self): self.local_node.sdo["Manufacturer device name"].raw = "Some cool device" device_name = self.remote_node.sdo["Manufacturer device name"].data