Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Discussion options

canopen/canopen/sdo/server.py
Line 72: res_command |= (4 - size) << 2
will generate a number of bytes not filled with data, in case of data size 0, it will result in n=4-0, n=4. But n should fit in bits 2..3 of the COB, it doesn't fit in 2 bits, as n in this case is 3 bits.

Is this a bug, if yes: what should the server send as a correct response?

You must be logged in to vote

Replies: 4 comments · 7 replies

Comment options

According to Copilot (not always assuming it is correct, but I think this one is, and it doesn't have a solution either):

CANopen SDO 0-Byte Upload Response Issue

You've identified a critical issue with the implementation. The command byte structure in CANopen SDO upload responses has limitations that affect the 0-byte case.

Command Byte Structure Problem

In the CANopen protocol, the command byte for expedited uploads has this structure:

  • Bits 5-7: Command specifier (010b for upload response)
  • Bit 1: Expedited transfer bit (1 = expedited)
  • Bit 0: Size indicated bit (1 = size is specified)
  • Bits 2-3: Number of bytes that do NOT contain data (n)

The critical problem: Bits 2-3 can only represent values 0-3, not 4!

In your current code:

res_command |= (4 - size) << 2

When size=0, this becomes (4 - 0) << 2, which sets bit 4 (outside the protocol-defined field) instead of keeping the value within bits 2-3.

According to the CANopen Specification

For a 0-byte transfer:

  • The maximum valid value for "n" (bytes not containing data) is 3 (bits 2-3 both set)
  • The command byte should indicate that the data field doesn't contain meaningful information
  • There's no exact representation for "all 4 bytes don't contain data" within the 2-bit field

Correct Approach

The CANopen standard typically handles this by setting n=3 (maximum possible) and understanding that expedited transfers with n=3 and size=0 are a special case. The recipient should properly interpret this based on the specific object being accessed.

The code should be fixed to ensure n never exceeds 3, even when size=0.

You must be logged in to vote
0 replies
Comment options

That answer seems roughly correct. Though I haven't actually looked at the code to see under what circumstances this could be a problem. Could you please provide a reproduction recipe to see what fails and how?

Why and how would one want to upload a zero byte object anyway? It doesn't make sense to read anything that has no content.

You must be logged in to vote
4 replies
@sveinse
Comment options

sveinse Jun 10, 2025
Collaborator

That answer seems roughly correct. Though I haven't actually looked at the code to see under what circumstances this could be a problem. Could you please provide a reproduction recipe to see what fails and how?

Why and how would one want to upload a zero byte object anyway? It doesn't make sense to read anything that has no content.

One use-case could be streaming and that there is no data available in the stream. E.g. object 0x1026 OS prompt which has sub-index for stdout and stderr. When the client (python) is polling for data using SDO and the remote node does not have any data available, then the length of the data would be 0. One could use SDO abort 0x0800_0024 No data available instead, but that will abort the SDO connection, which depends on the application.

@acolomb
Comment options

I'm not sure what you mean with "abort the SDO connection". An SDO upload request is confirmed, either by the response containing requested data, or by an SDO abort response. Either way, there is no further state of an "active connection" on the SDO channel - it's always just one active request (which might involve several responses though in case of block upload).

I think the "No data available" abort code response is the correct behavior in such a case.

@sveinse
Comment options

sveinse Jun 11, 2025
Collaborator

Usually in communication protocols, zero length data isn't considered an error. To quote the 301 spec "An SDO abort transfer request/indication, indicating the unsuccessful completion of...". Have zero length (n=7) in a segment is valid and it doesn't imply there is an error. The usage of the "No data available" SDO abort is for cases when its unexpected that there is no data.

When e=0 (not expedited), s=0 (no size) the standard states "d contains unspecified number of bytes to be uploaded/downloaded". During the data segments, any segment may have n=7 which indicates there are no data available.

The use case is serial communication channels or terminal console over SDO. If the remote buffer have no data to read (upload), then a zero length response is the right response. This is NOT an error condition.

PS! As mentioned elsewhere: expedited SDOs can't be used for zero-length data transfers, due to the size encoding.

@acolomb
Comment options

Sorry if this is getting a bit split up, but I added some responses in #587 that could just as well fit into this discussion thread.

Comment options

Don't have a recipe I cn share quickly. But our application has a dynamic sized payload, which at times no data is available, will send a 0-sized bytearray. For now we use an 'if zero then' construction.

(sorry for the long delay, I didn't get the notifications from github..)

You must be logged in to vote
0 replies
Comment options

Created a test that triggers the fault:

add test below in test_local.py, TestSDO class, and add a breakpoint in sdo/client.py at line 256 (res_data = response[4:8]) or add a print statement to see the content of res_command:

    def test_upload_zero(self):
        self.local_node.sdo["Manufacturer device name"].raw = b""
        device_name = self.remote_node.sdo["Manufacturer device name"].data
        self.assertEqual(device_name, b'\x00\x00\x00\x00') 
        # this tests OK, but the returned command byte is 0x53, which is incorrect:
        #  b'S\x08\x10\x00\x00\x00\x00\x00' -> 'S' == 0x53 -> 010_x_??_11, where ?? is the number of bytes NOT containing data
        # expected: 0x43 -> 010_0_00_11 -> all bytes contain data -> b'\x00\x00\x00\x00'
        # or: # 0x4F -> 010_0_11_11 -> 3 bytes do ot contain data -> b'\x00'
You must be logged in to vote
3 replies
@acolomb
Comment options

Thanks for the test case. It's clear where things are going wrong. Now, I tend to see an SDO abort code as the correct behavior in this case, instead of just pretending we can set n=4 (all four bytes contain no data) in the regular server response. The protocol just doesn't encode that situation. That matches the definition in CiA 301, section 7.2.4.2.12, where the "Data" field is marked mandatory in the response.

@acolomb
Comment options

I've filed #587 as a suggested solution.

@sveinse
Comment options

sveinse Jun 11, 2025
Collaborator

It's not that black and and white unfortunately. Setting n=7 in download segment request and upload segment response corresponds to no data. This is explained in the #587 issue. Expedited and block doesn't support it, so the test case is correct for that usage which is using expedited if I'm not mistaken.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Category
🙏
Q&A
Labels
None yet
3 participants
Morty Proxy This is a proxified and sanitized view of the page, visit original site.