|
CANARD_PRIVATE TxChain txGenerateMultiFrameChain(struct CanardTxQueue* const que, |
|
const struct CanardInstance* const ins, |
|
const size_t presentation_layer_mtu, |
|
const CanardMicrosecond deadline_usec, |
|
const uint32_t can_id, |
|
const CanardTransferID transfer_id, |
|
const struct CanardPayload payload) |
|
{ |
|
CANARD_ASSERT(que != NULL); |
|
CANARD_ASSERT(presentation_layer_mtu > 0U); |
|
CANARD_ASSERT(payload.size > presentation_layer_mtu); // Otherwise, a single-frame transfer should be used. |
|
CANARD_ASSERT(payload.data != NULL); |
|
|
|
TxChain out = {.head = NULL, .tail = NULL, .size = 0U}; |
|
const size_t payload_size_with_crc = payload.size + CRC_SIZE_BYTES; |
|
size_t offset = 0U; |
|
TransferCRC crc = crcAdd(CRC_INITIAL, payload); |
|
bool toggle = INITIAL_TOGGLE_STATE; |
|
const uint8_t* payload_ptr = (const uint8_t*) payload.data; |
|
while (offset < payload_size_with_crc) |
|
{ |
|
out.size++; |
|
const size_t frame_payload_size_with_tail = |
|
((payload_size_with_crc - offset) < presentation_layer_mtu) |
|
? txRoundFramePayloadSizeUp((payload_size_with_crc - offset) + 1U) // Padding in the last frame only. |
|
: (presentation_layer_mtu + 1U); |
|
struct CanardTxQueueItem* const tqi = |
|
txAllocateQueueItem(que, ins, can_id, deadline_usec, frame_payload_size_with_tail); |
|
if (NULL == out.head) |
|
{ |
|
out.head = tqi; |
|
} |
|
else |
|
{ |
|
// C std, 6.7.2.1.15: A pointer to a structure object <...> points to its initial member, and vice versa. |
|
// Can't just read tqi->base because tqi may be NULL; https://github.com/OpenCyphal/libcanard/issues/203. |
|
out.tail->next_in_transfer = tqi; |
|
} |
|
out.tail = tqi; |
|
if (NULL == out.tail) |
|
{ |
|
break; |
|
} |
|
|
|
// Copy the payload into the frame. |
|
uint8_t* const frame_bytes = tqi->frame.payload.data; |
|
const size_t frame_payload_size = frame_payload_size_with_tail - 1U; |
|
size_t frame_offset = 0U; |
|
if (offset < payload.size) |
|
{ |
|
size_t move_size = payload.size - offset; |
|
if (move_size > frame_payload_size) |
|
{ |
|
move_size = frame_payload_size; |
|
} |
|
// Clang-Tidy raises an error recommending the use of memcpy_s() instead. |
|
// We ignore it because the safe functions are poorly supported; reliance on them may limit the portability. |
|
// SonarQube incorrectly detects a buffer overflow here. |
|
(void) memcpy(frame_bytes, payload_ptr, move_size); // NOLINT NOSONAR |
|
frame_offset = frame_offset + move_size; |
|
offset += move_size; |
|
payload_ptr += move_size; |
|
} |
|
|
|
// Handle the last frame of the transfer: it is special because it also contains padding and CRC. |
|
if (offset >= payload.size) |
|
{ |
|
// Insert padding -- only in the last frame. Don't forget to include padding into the CRC. |
|
while ((frame_offset + CRC_SIZE_BYTES) < frame_payload_size) |
|
{ |
|
frame_bytes[frame_offset] = PADDING_BYTE_VALUE; |
|
++frame_offset; |
|
crc = crcAddByte(crc, PADDING_BYTE_VALUE); |
|
} |
|
|
|
// Insert the CRC. |
|
if ((frame_offset < frame_payload_size) && (offset == payload.size)) |
|
{ |
|
// SonarQube incorrectly detects a buffer overflow here. |
|
frame_bytes[frame_offset] = (uint8_t) (crc >> BITS_PER_BYTE); // NOSONAR |
|
++frame_offset; |
|
++offset; |
|
} |
|
if ((frame_offset < frame_payload_size) && (offset > payload.size)) |
|
{ |
|
frame_bytes[frame_offset] = (uint8_t) (crc & BYTE_MAX); |
|
++frame_offset; |
|
++offset; |
|
} |
|
} |
|
|
|
// Finalize the frame. |
|
CANARD_ASSERT((frame_offset + 1U) == out.tail->frame.payload.size); |
|
// SonarQube incorrectly detects a buffer overflow here. |
|
frame_bytes[frame_offset] = // NOSONAR |
|
txMakeTailByte(out.head == out.tail, offset >= payload_size_with_crc, toggle, transfer_id); |
|
toggle = !toggle; |
|
} |
|
return out; |
|
} |
See OpenCyphal-Garage/libcyphal#343 (comment), but note that we have since altered the design such that the TX pipeline allows exactly one copy, which happens when the user data is copied into individual frame payloads. This is avoidable but the complexity increases dramatically at this point because the application would be required to keep its data alive and unmodified until the TX frames have cleared the queue, which requires reference counting, and the application would likely be required to copy its data into a separate storage before transmitting it in this way anyway, which defeats the purpose of the optimization.
So the input data may be scattered, but it is borrowed rather than moved, as we copy it into small contiguous per-frame chunks. The affected functions are
txPushSingleFrameandtxGenerateMultiFrameChain-- currently they rely on simple contiguous pointer arithmetics, which will need to be replaced with a stateful iterator struct with a helper function for extracting the data from a scattered buffer step by step:libcanard/libcanard/canard.c
Lines 372 to 425 in 4d46208
libcanard/libcanard/canard.c
Lines 428 to 527 in 4d46208