Skip to content

Handling zero length SDO transfers, and expedited transfers with no size specified #29

@mcbridejc

Description

@mcbridejc

Background

SDO transfers -- both upload (server-to-client) and download (client-to-server) -- support an expedited mode when the size of the object to be transferred is <= 4.

In the case of upload, the server knows the size to be transferred, so it is responsible (in the ConfirmUpload message) for deciding whether the upload will be performed as expedited or segmented.

In the case of download, the client decides whether to use expedited or segmented transfer (see InitiateDownload)

In both messages, there is a 4-byte d field, storing either the transferred data, or a size value, as well as an e flag, which if set indicates that the transfer will be an expedited transfer, and an s flag which if set indicates that the size is specified. Regarding the data field, CiA 301 has this to say:

e = 0, s = 0:
  d is reserved for further use.
e = 0, s = 1:
  d contains the number of bytes to be uploaded.
  Byte 4 contains the lsb and byte 7 contains the msb.
e = 1, s = 1:
  d contains the data of length 4-n to be uploaded,
  the encoding depends on the type of the data referenced by index and sub-
  index
e = 1, s = 0:
  d contains unspecified number of bytes to be uploaded.

In almost all cases, both the client and server know the size to be transferred in advance, and so they should always set the s bit. However, there may be some corner cases where a device does not know the size for some reason -- perhaps it is reading from a hardware queue and it cannot tell the size until it does the read, and has no extra RAM to buffer. In any case, the protocol allows for it, and for segmented transfers it poses no special problem because the last segment is transmitted with a size and a flag to signal completion of the transfer.

The troublesome case is the expedited transfer with no indicated size (e=1, s=0).

There are two separate questions to resolve:

  1. What should the server/device do?
  2. What should the client do?

Zero length transfers

I have found no mention of zero length transfers in the CANOpen specification, but I am absolutely committed to the idea that zencan devices must be able to support zero length transfers, at least when using the zencan client to access them. Most objects have non-zero size, but there are exceptions:

  • Domains have application defined size, and can certainly be "empty". For example, a domain being used as a queue for transferring streaming data may be currently empty when read.
  • Strings may be empty. OctetStrings do not allow for zero-terminating to indicate this.

So, semantics for a device to indicate that an object has no data are required. However, the size for expedited transfers is specified by providing the field n, such that size = 4 - n. n is a 2-bit value, so it has a maximum value of 3, making it impossible to represent a transfer of 0 size. There is a reserved bit right above n in the message. This makes me sad, and suggests that the original protocol authors did not consider a 0-length transfer to be relevant. Still, zencan must find a way to perform them.

Current zencan behavior

My initial approach was to interpret the e=1, s=0 case to mean that zero bytes were being transferred. My reasoning is that there is never any reason for a device to perform an expedited transfer with no size, since it must know the size in order to choose an expedited upload. This seems to be in compliance with the spec, since the spec says the size is undefined, and it solved my zero-length-transfer dilemma neatly.

PR #27 then raised the possibility that some devices out in the wild are in fact performing expedited uploads without setting the s bit, and if that's true, it's not going to be possible to use the current zencan client with them.

Survey of other library behavior

There are (at least) two opensource CANOpen implementations out there, CANOpenNode (C) and canopen-python. So what do they do?

Server implementations

Both servers return errors when they read 0 bytes from the OD during an upload.

The CANOpenNode SDO server aborts with 0x06040047 ("General Internal Incompatibility")

The canopen-python server aborts with 0x08000024 ("No Data Available")

The General Internal Incompatibility error is ambiguous -- there are OTHER reasons the server may return that so it does not work as an indicator of a zero length transfer. The No Data Available error could work for zencan -- this is at least a distinct abort code so the client can distinguish the zero-length case from other errors.

Client implementation

Both the CANOpenNode and canopen-python clients appear to return 4 bytes when e=1, s=0 on an upload.

Other Discussions on the topic

canopen-python/canopen#587

Options

Encode 0 size with e=1, s=0 (Status quo)

Cons: If any other CANOpen devices use e=1,s=0 to transfer a non-zero number of bytes, they will not be compatible with zencan-client.

Perform segmented transfer for zero-length

Segmented transfers are able to encode zero length by setting c=1, n=7 on the first segment message (i.e. the first segment completes the transfer with 0 valid data bytes).

Cons: Requires 4 messages on the bus instead of 2.

Abort with NoDataAvailable error

In this case, I think the client would consume the error and return 0 bytes to the caller, rather than returning the abort code. This is a weird case, as an abort is generally supposed to indicate a failed transfer. I don't love it, but it does work at least for uploads. I think it does not work with a zero-length download.

Cons:

  • Using an abort to end a successful transaction feels wrong
  • It doesn't work for downloads

Client Handling of e=1,s=0

If the zencan implementation changes such that an upload with e=1,s=0 is not interpreted as zero-length, then the return type for SdoClient::upload will have to change from a Vec to a custom type which can represent the "unspecified size" case. There may be cases where a client knows what size is expected (e.g. based on the EDS for the device), and can choose to use the appropriate number of bytes from the 4 bytes of data, but I think it's important that clients can distinguish between the e=1,s=1,n=0 case and e=1,s=0 case. They shouldn't both mean that 4 valid bytes are transferred.

Resolution

My inclination is to leave it as-is, pending confirmation that there are devices in the wild which require a client to support reading data with e=1,s=0.

If that is actually something zencan-client needs to handle, then I lean towards the following :

  • For zero length uploads, the server never uses expedited transfer, but instead uses segmented transfer
  • For zero length downloads, the client does the same thing.
  • The client will NEVER set e=1,s=0 on downloads, but I will adjust the API to return a custom type so that if it encounters it on upload it can return all four data bytes along with metadata indicating that the size was not indicated.

That type could be an enum, like:

enum UploadResponse {
    SizeKnown(Vec<u8>),
    SizeUnknown([u8;4]),
 }

Or perhaps a struct:

struct UploadResponse {
    data: Vec<u8>,
    size: Option<usize>
}

The typed transfer methods with an implied size (e.g. upload_u16) could assume that the expected bytes are valid -- e.g. upload_u16 would use the first two bytes, and ignore the remaining 2.

It is for sure less ergonomic and I wish the protocol has just specified e=1,s=0 to mean zero as I can see no downsides at all to that, but it does not and I would prefer the client to be usable for any devices people want to use it with.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions