Homepage GitHub

CAN Transport multi-frame transfer's have unknown length

As far as I can tell, the specification for CAN provides no means for a consumer to know how many frames to expect in a multi-frame transfer.

For example, node A sends a message over FDCAN (64 byte MCU). The transfer will consist of 8 frames let’s say. Node B receives the first message; it contains an unknown port id. Node B will log the message regardless, but since it cannot identify the port id, Node B does not know the expected message length (a similar problem for variable-length arrays, Node B would only have a lower and upper bound on the number of frames to expect).

This prevents Node B from allocating the exact amount of memory it will need.

Am I missing something? I want to build a driver that avoids any fragmentation in memory allocation. My plan is to use some sort of circular ring buffer, but this seems infeasible given the lack of knowledge of the number of frames to expect.

You are partially correct – in UAVCAN/CAN and UAVCAN/UDP the size of a transfer cannot be known until the arrival of its last frame (this is not the case in UAVCAN/serial, where all transfers are single-frame transfers and the size of the frame is communicated explicitly in its header).

This is not expected to cause any memory management issues, though. The UAVCAN communication model prescribes that a subscriber (or a service client/server) interested in a particular subject always knows its type. Per the type model, the worst case size of any data type is always known statically (it is not possible to define a data type without the upper size bound), so the node would know how much memory to allocate. This is exactly how it is handled in Libcanard, for example – when you create a new subscription, you have to pass the memory size to the library so that it would know how much data to expect and how much contiguous buffer space to allocate:

CanardRxSubscription my_subscription;
(void) canardRxSubscribe(&ins,
                         CanardTransferKindMessage,
                         my_subject_id,
                         1024,  // Allocate 1 KiB per message
                         CANARD_DEFAULT_TRANSFER_ID_TIMEOUT_USEC,
                         &my_subscription);

If the received object is larger, the data beyond the size limit will be silently truncated away (this is called “implicit truncation rule”, details are in the Specification), but the integrity of the transfer will still be validated because even if the data is truncated, its CRC is still validated (since it is a single-pass function).

The payload buffer is then allocated by the library lazily once the first frame of the transfer is received:

So, to answer this:

The information you are looking for is obtained from the type definition.