Skip to content

Commit 451d584

Browse files
remove the transfer-CRC, use prefix CRC instead to greatly simplify truncation and reassembly
1 parent f9a52ce commit 451d584

File tree

2 files changed

+40
-50
lines changed

2 files changed

+40
-50
lines changed

libudpard/udpard.c

Lines changed: 29 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,8 @@ static byte_t* header_serialize(byte_t* const buffer,
287287
const meta_t meta,
288288
const bool flag_eot,
289289
const uint32_t frame_index,
290-
const uint32_t frame_payload_offset)
290+
const uint32_t frame_payload_offset,
291+
const uint32_t prefix_crc)
291292
{
292293
byte_t* ptr = buffer;
293294
byte_t flags = 0;
@@ -307,7 +308,7 @@ static byte_t* header_serialize(byte_t* const buffer,
307308
ptr = serialize_u64(ptr, meta.transfer_id);
308309
ptr = serialize_u64(ptr, meta.sender_uid);
309310
ptr = serialize_u64(ptr, meta.topic_hash);
310-
ptr = serialize_u32(ptr, 0);
311+
ptr = serialize_u32(ptr, prefix_crc);
311312
ptr = serialize_u32(ptr, crc_full(HEADER_SIZE_BYTES - CRC_SIZE_BYTES, buffer));
312313
UDPARD_ASSERT((size_t)(ptr - buffer) == HEADER_SIZE_BYTES);
313314
return ptr;
@@ -318,6 +319,7 @@ static bool header_deserialize(const udpard_bytes_mut_t dgram_payload,
318319
bool* const flag_eot,
319320
uint32_t* const frame_index,
320321
uint32_t* const frame_payload_offset,
322+
uint32_t* const prefix_crc,
321323
udpard_bytes_mut_t* const out_payload)
322324
{
323325
UDPARD_ASSERT(out_payload != NULL);
@@ -339,6 +341,7 @@ static bool header_deserialize(const udpard_bytes_mut_t dgram_payload,
339341
ptr = deserialize_u64(ptr, &out_meta->transfer_id);
340342
ptr = deserialize_u64(ptr, &out_meta->sender_uid);
341343
ptr = deserialize_u64(ptr, &out_meta->topic_hash);
344+
ptr = deserialize_u32(ptr, prefix_crc);
342345
(void)ptr;
343346
// Set up the output payload view.
344347
out_payload->size = dgram_payload.size - HEADER_SIZE_BYTES;
@@ -467,17 +470,16 @@ static tx_chain_t tx_spool(const udpard_tx_mem_resources_t memory,
467470
{
468471
UDPARD_ASSERT(mtu > 0);
469472
UDPARD_ASSERT((payload.data != NULL) || (payload.size == 0U));
470-
const size_t payload_size_with_crc = payload.size + CRC_SIZE_BYTES;
471-
byte_t crc_bytes[CRC_SIZE_BYTES];
472-
serialize_u32(crc_bytes, crc_full(payload.size, payload.data));
473-
tx_chain_t out = { NULL, NULL, 0 };
474-
size_t offset = 0U;
475-
while (offset < payload_size_with_crc) {
476-
udpard_tx_item_t* const item = tx_item_new(memory,
473+
uint32_t prefix_crc = CRC_INITIAL;
474+
tx_chain_t out = { NULL, NULL, 0 };
475+
size_t offset = 0U;
476+
while (offset < payload.size) {
477+
const size_t progress = smaller(payload.size - offset, mtu);
478+
udpard_tx_item_t* const item = tx_item_new(memory, //
477479
deadline,
478480
meta.priority,
479481
endpoint,
480-
smaller(payload_size_with_crc - offset, mtu) + HEADER_SIZE_BYTES,
482+
progress + HEADER_SIZE_BYTES,
481483
user_transfer_reference);
482484
if (NULL == out.head) {
483485
out.head = item;
@@ -488,31 +490,22 @@ static tx_chain_t tx_spool(const udpard_tx_mem_resources_t memory,
488490
if (NULL == out.tail) {
489491
break;
490492
}
491-
const bool last = (payload_size_with_crc - offset) <= mtu;
492-
byte_t* const dst_buffer = item->datagram_payload.data;
493-
byte_t* write_ptr = header_serialize(dst_buffer, meta, last, (uint32_t)out.count, (uint32_t)offset);
494-
if (offset < payload.size) {
495-
const size_t progress = smaller(payload.size - offset, mtu);
496-
// NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling)
497-
(void)memcpy(write_ptr, ((const byte_t*)payload.data) + offset, progress);
498-
offset += progress;
499-
write_ptr += progress;
500-
UDPARD_ASSERT(offset <= payload.size);
501-
UDPARD_ASSERT((!last) || (offset == payload.size));
502-
}
503-
if (offset >= payload.size) {
504-
const size_t crc_offset = offset - payload.size;
505-
UDPARD_ASSERT(crc_offset < CRC_SIZE_BYTES);
506-
const size_t available = item->datagram_payload.size - (size_t)(write_ptr - dst_buffer);
507-
UDPARD_ASSERT(available <= CRC_SIZE_BYTES);
508-
const size_t write_size = smaller(CRC_SIZE_BYTES - crc_offset, available);
509-
// NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling)
510-
(void)memcpy(write_ptr, &crc_bytes[crc_offset], write_size);
511-
offset += write_size;
512-
}
493+
const bool last = (payload.size - offset) <= mtu;
494+
const byte_t* const read_ptr = ((const byte_t*)payload.data) + offset;
495+
prefix_crc = crc_add(prefix_crc, progress, read_ptr);
496+
byte_t* const write_ptr = header_serialize(item->datagram_payload.data, //
497+
meta,
498+
last,
499+
(uint32_t)out.count,
500+
(uint32_t)offset,
501+
prefix_crc ^ CRC_OUTPUT_XOR);
502+
(void)memcpy(write_ptr, read_ptr, progress); // NOLINT(*DeprecatedOrUnsafeBufferHandling)
503+
offset += progress;
504+
UDPARD_ASSERT(offset <= payload.size);
505+
UDPARD_ASSERT((!last) || (offset == payload.size));
513506
out.count++;
514507
}
515-
UDPARD_ASSERT((offset == payload_size_with_crc) || (out.tail == NULL));
508+
UDPARD_ASSERT((offset == payload.size) || (out.tail == NULL));
516509
return out;
517510
}
518511

@@ -526,7 +519,7 @@ static uint32_t tx_push(udpard_tx_t* const tx,
526519
UDPARD_ASSERT(tx != NULL);
527520
uint32_t out = 0; // The number of frames enqueued; zero on error (error counters incremented).
528521
const size_t mtu = larger(tx->mtu, UDPARD_MTU_MIN);
529-
const size_t frame_count = ((payload.size + CRC_SIZE_BYTES + mtu) - 1U) / mtu;
522+
const size_t frame_count = (payload.size + mtu - 1U) / mtu;
530523
if ((tx->queue_size + frame_count) > tx->queue_capacity) {
531524
tx->errors_capacity++;
532525
} else {
@@ -724,7 +717,7 @@ typedef struct
724717
/// given a linked list of these objects represented as a list of udpard_fragment_t.
725718
typedef struct rx_fragment_t
726719
{
727-
udpard_fragment_t base;
720+
udpard_fragment_t base; ///< Must be the first member; do not move!
728721
udpard_tree_t tree;
729722
} rx_fragment_t;
730723

@@ -735,7 +728,7 @@ typedef struct
735728
bool eot_discovered;
736729
uint32_t accepted_frames; ///< Number of frames accepted so far.
737730
size_t payload_size_stored; ///< Grows with each accepted fragment (out of order).
738-
uint32_t crc; ///< Out-of-order CRC reconstruction state.
731+
uint32_t crc; ///< CRC reconstruction state.
739732
udpard_tree_t* fragments;
740733
} rx_slot_iface_t;
741734

libudpard/udpard.h

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -120,11 +120,10 @@ extern "C"
120120

121121
/// RFC 791 states that hosts must be prepared to accept datagrams of up to 576 octets and it is expected that this
122122
/// library will receive non IP-fragmented datagrams thus the minimum MTU should be larger than 576.
123+
/// This is also the maximum size of a single-frame transfer.
123124
/// That being said, the MTU here is set to a larger value that is derived as:
124125
/// 1500B Ethernet MTU (RFC 894) - 60B IPv4 max header - 8B UDP Header - 48B Cyphal header
125126
#define UDPARD_MTU_DEFAULT 1384U
126-
/// To guarantee a single frame transfer, the maximum payload size shall be 4 bytes less to accommodate the CRC.
127-
#define UDPARD_MTU_DEFAULT_MAX_SINGLE_FRAME (UDPARD_MTU_DEFAULT - 4U)
128127

129128
/// MTU less than this should not be used.
130129
#define UDPARD_MTU_MIN 460U
@@ -298,12 +297,12 @@ typedef struct udpard_tx_mem_resources_t
298297
/// FUTURE: Eventually we might consider adding another way of arranging the transmission pipeline where the UDP
299298
/// datagrams ready for transmission are not enqueued into the local prioritized queue but instead are sent directly
300299
/// to the network interface driver using a dedicated callback. The callback would accept not just a single
301-
/// chunk of data but a list of three chunks to avoid copying the source transfer payload: the datagram header,
302-
/// the payload, and (only for the last frame) the CRC. The driver would then use some form of vectorized IO or
303-
/// MSG_MORE/UDP_CORK to transmit the data; the advantage of this approach is that up to two data copy operations are
304-
/// eliminated from the stack and the memory allocator is not used at all. The disadvantage is that if the driver
305-
/// callback is blocking, the application thread will be blocked as well; plus the driver will be responsible
306-
/// for the correct prioritization of the outgoing datagrams according to the DSCP value.
300+
/// chunk of data but a list of chunks to avoid copying the source transfer payload: the header and the payload.
301+
/// The driver would then use some form of vectorized IO or MSG_MORE/UDP_CORK to transmit the data;
302+
/// the advantage of this approach is that up to two data copy operations are eliminated from the stack and the
303+
/// memory allocator is not used at all. The disadvantage is that if the driver callback is blocking,
304+
/// the application thread will be blocked as well; plus the driver will be responsible for the correct
305+
/// prioritization of the outgoing datagrams according to the DSCP value.
307306
typedef struct udpard_tx_t
308307
{
309308
/// A globally unique identifier of the local node, composed of (VID<<48)|(PID<<32)|INSTANCE_ID.
@@ -314,8 +313,7 @@ typedef struct udpard_tx_t
314313
size_t queue_capacity;
315314

316315
/// The maximum number of Cyphal transfer payload bytes per UDP datagram.
317-
/// The Cyphal/UDP header and the final CRC are added to this value to obtain the total UDP datagram payload size.
318-
/// See UDPARD_MTU_*.
316+
/// The Cyphal/UDP header is added to this value to obtain the total UDP datagram payload size. See UDPARD_MTU_*.
319317
/// The value can be changed arbitrarily at any time between enqueue operations.
320318
size_t mtu;
321319

@@ -369,7 +367,7 @@ typedef struct udpard_tx_item_t
369367
/// This UDP/IP datagram compiled by libudpard should be sent to this remote endpoint (usually multicast).
370368
udpard_udpip_ep_t destination;
371369

372-
/// The completed UDP/IP datagram payload. This includes the Cyphal header as well as all required CRCs.
370+
/// The completed UDP/IP datagram payload.
373371
udpard_bytes_mut_t datagram_payload;
374372

375373
/// This opaque pointer is assigned the value that is passed to udpardTxPublish/Request/Respond.
@@ -577,8 +575,8 @@ typedef struct udpard_rx_transfer_t
577575
/// it is the sum of the sizes of all its fragments. For example, if the sender emitted a transfer of 2000
578576
/// bytes split into two frames, 1408 bytes in the first frame and 592 bytes in the second frame,
579577
/// then the payload_size_stored will be 2000 and the payload buffer will contain two fragments of 1408 and
580-
/// 592 bytes. The transfer CRC is not included here. If the received payload exceeds the configured extent,
581-
/// the excess payload will be discarded and the payload_size_stored will be set to the extent.
578+
/// 592 bytes. If the received payload exceeds the configured extent, the excess payload will be discarded
579+
/// and the payload_size_stored will be set to the extent.
582580
///
583581
/// The application is given ownership of the payload buffer, so it is required to free it after use;
584582
/// this requires freeing both the handles and the payload buffers they point to.
@@ -705,7 +703,6 @@ void udpard_rx_subscription_free(udpard_rx_subscription_t* const self);
705703
///
706704
/// The time complexity is O(log n) where n is the number of remote notes publishing on this subject (topic).
707705
/// No data copy takes place. Malformed frames are discarded in constant time.
708-
/// Linear time is spent on the CRC verification of the transfer payload when the transfer is complete.
709706
///
710707
/// Returns true on successful processing, false if any of the arguments are invalid.
711708
bool udpard_rx_subscription_receive(udpard_rx_t* const rx,

0 commit comments

Comments
 (0)