Skip to content

Commit 6181d1e

Browse files
committed
udpardTxPeek now returns mutable item - needed for payload ownership
transfer.
1 parent 3d9f5ef commit 6181d1e

File tree

5 files changed

+232
-136
lines changed

5 files changed

+232
-136
lines changed

libudpard/udpard.c

Lines changed: 55 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#include "udpard.h"
77
#include "_udpard_cavl.h"
8+
89
#include <string.h>
910

1011
// --------------------------------------------- BUILD CONFIGURATION ---------------------------------------------
@@ -280,9 +281,6 @@ typedef struct
280281
{
281282
struct UdpardTxItem base;
282283
enum UdpardPriority priority; ///< Do we need this exposed in the public structure? We already have DSCP there.
283-
// The MISRA violation here is hard to get rid of without having to allocate a separate memory block for the
284-
// payload, which is much more costly risk-wise.
285-
byte_t payload_buffer[]; // NOSONAR MISRA C 18.7 Flexible array member.
286284
} TxItem;
287285

288286
/// Chain of TX frames prepared for insertion into a TX queue.
@@ -293,15 +291,21 @@ typedef struct
293291
size_t count;
294292
} TxChain;
295293

296-
static inline TxItem* txNewItem(const struct UdpardMemoryResource memory,
297-
const uint_least8_t dscp_value_per_priority[UDPARD_PRIORITY_MAX + 1U],
298-
const UdpardMicrosecond deadline_usec,
299-
const enum UdpardPriority priority,
300-
const struct UdpardUDPIPEndpoint endpoint,
301-
const size_t datagram_payload_size,
302-
void* const user_transfer_reference)
294+
static inline bool txValidateMemoryResources(const struct UdpardTxMemoryResources memory)
295+
{
296+
return (memory.fragment.allocate != NULL) && (memory.fragment.deallocate != NULL) &&
297+
(memory.payload.allocate != NULL) && (memory.payload.deallocate != NULL);
298+
}
299+
300+
static inline TxItem* txNewItem(const struct UdpardTxMemoryResources memory,
301+
const uint_least8_t dscp_value_per_priority[UDPARD_PRIORITY_MAX + 1U],
302+
const UdpardMicrosecond deadline_usec,
303+
const enum UdpardPriority priority,
304+
const struct UdpardUDPIPEndpoint endpoint,
305+
const size_t datagram_payload_size,
306+
void* const user_transfer_reference)
303307
{
304-
TxItem* const out = (TxItem*) memAlloc(memory, sizeof(TxItem) + datagram_payload_size);
308+
TxItem* out = memAlloc(memory.fragment, sizeof(TxItem));
305309
if (out != NULL)
306310
{
307311
// No tree linkage by default.
@@ -317,9 +321,18 @@ static inline TxItem* txNewItem(const struct UdpardMemoryResource memory,
317321
out->base.dscp = dscp_value_per_priority[priority];
318322
out->base.destination = endpoint;
319323
out->base.user_transfer_reference = user_transfer_reference;
320-
// The payload points to the buffer already allocated.
321-
out->base.datagram_payload.size = datagram_payload_size;
322-
out->base.datagram_payload.data = &out->payload_buffer[0];
324+
325+
void* const payload_data = memAlloc(memory.payload, datagram_payload_size);
326+
if (NULL != payload_data)
327+
{
328+
out->base.datagram_payload.data = payload_data;
329+
out->base.datagram_payload.size = datagram_payload_size;
330+
}
331+
else
332+
{
333+
memFree(memory.fragment, sizeof(TxItem), out);
334+
out = NULL;
335+
}
323336
}
324337
return out;
325338
}
@@ -390,14 +403,14 @@ static inline byte_t* txSerializeHeader(byte_t* const destination_buffe
390403

391404
/// Produces a chain of Tx queue items for later insertion into the Tx queue. The tail is NULL if OOM.
392405
/// The caller is responsible for freeing the memory allocated for the chain.
393-
static inline TxChain txMakeChain(const struct UdpardMemoryResource memory,
394-
const uint_least8_t dscp_value_per_priority[UDPARD_PRIORITY_MAX + 1U],
395-
const size_t mtu,
396-
const UdpardMicrosecond deadline_usec,
397-
const TransferMetadata meta,
398-
const struct UdpardUDPIPEndpoint endpoint,
399-
const struct UdpardPayload payload,
400-
void* const user_transfer_reference)
406+
static inline TxChain txMakeChain(const struct UdpardTxMemoryResources memory,
407+
const uint_least8_t dscp_value_per_priority[UDPARD_PRIORITY_MAX + 1U],
408+
const size_t mtu,
409+
const UdpardMicrosecond deadline_usec,
410+
const TransferMetadata meta,
411+
const struct UdpardUDPIPEndpoint endpoint,
412+
const struct UdpardPayload payload,
413+
void* const user_transfer_reference)
401414
{
402415
UDPARD_ASSERT(mtu > 0);
403416
UDPARD_ASSERT((payload.data != NULL) || (payload.size == 0U));
@@ -430,8 +443,9 @@ static inline TxChain txMakeChain(const struct UdpardMemoryResource memory,
430443
{
431444
break;
432445
}
433-
const bool last = (payload_size_with_crc - offset) <= mtu;
434-
byte_t* write_ptr = txSerializeHeader(&item->payload_buffer[0], meta, (uint32_t) out.count, last);
446+
const bool last = (payload_size_with_crc - offset) <= mtu;
447+
byte_t* const dst_buffer = item->base.datagram_payload.data;
448+
byte_t* write_ptr = txSerializeHeader(dst_buffer, meta, (uint32_t) out.count, last);
435449
if (offset < payload.size)
436450
{
437451
const size_t progress = smaller(payload.size - offset, mtu);
@@ -446,7 +460,7 @@ static inline TxChain txMakeChain(const struct UdpardMemoryResource memory,
446460
{
447461
const size_t crc_offset = offset - payload.size;
448462
UDPARD_ASSERT(crc_offset < TRANSFER_CRC_SIZE_BYTES);
449-
const size_t available = item->base.datagram_payload.size - (size_t) (write_ptr - &item->payload_buffer[0]);
463+
const size_t available = item->base.datagram_payload.size - (size_t) (write_ptr - dst_buffer);
450464
UDPARD_ASSERT(available <= TRANSFER_CRC_SIZE_BYTES);
451465
const size_t write_size = smaller(TRANSFER_CRC_SIZE_BYTES - crc_offset, available);
452466
// NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling)
@@ -517,7 +531,7 @@ static inline int32_t txPush(struct UdpardTx* const tx,
517531
while (head != NULL)
518532
{
519533
struct UdpardTxItem* const next = head->next_in_transfer;
520-
memFree(tx->memory, sizeof(TxItem) + head->datagram_payload.size, head);
534+
udpardTxFree(tx->memory, head);
521535
head = next;
522536
}
523537
}
@@ -526,13 +540,13 @@ static inline int32_t txPush(struct UdpardTx* const tx,
526540
return out;
527541
}
528542

529-
int_fast8_t udpardTxInit(struct UdpardTx* const self,
530-
const UdpardNodeID* const local_node_id,
531-
const size_t queue_capacity,
532-
const struct UdpardMemoryResource memory)
543+
int_fast8_t udpardTxInit(struct UdpardTx* const self,
544+
const UdpardNodeID* const local_node_id,
545+
const size_t queue_capacity,
546+
const struct UdpardTxMemoryResources memory)
533547
{
534548
int_fast8_t ret = -UDPARD_ERROR_ARGUMENT;
535-
if ((NULL != self) && (NULL != local_node_id) && (memory.allocate != NULL) && (memory.deallocate != NULL))
549+
if ((NULL != self) && (NULL != local_node_id) && txValidateMemoryResources(memory))
536550
{
537551
ret = 0;
538552
memZero(sizeof(*self), self);
@@ -639,14 +653,14 @@ int32_t udpardTxRespond(struct UdpardTx* const self,
639653
return out;
640654
}
641655

642-
const struct UdpardTxItem* udpardTxPeek(const struct UdpardTx* const self)
656+
struct UdpardTxItem* udpardTxPeek(const struct UdpardTx* const self)
643657
{
644-
const struct UdpardTxItem* out = NULL;
658+
struct UdpardTxItem* out = NULL;
645659
if (self != NULL)
646660
{
647661
// Paragraph 6.7.2.1.15 of the C standard says:
648662
// A pointer to a structure object, suitably converted, points to its initial member, and vice versa.
649-
out = (const struct UdpardTxItem*) (void*) cavlFindExtremum(self->root, false);
663+
out = (struct UdpardTxItem*) (void*) cavlFindExtremum(self->root, false);
650664
}
651665
return out;
652666
}
@@ -671,11 +685,16 @@ struct UdpardTxItem* udpardTxPop(struct UdpardTx* const self, const struct Udpar
671685
return out;
672686
}
673687

674-
void udpardTxFree(const struct UdpardMemoryResource memory, struct UdpardTxItem* const item)
688+
void udpardTxFree(const struct UdpardTxMemoryResources memory, struct UdpardTxItem* const item)
675689
{
676690
if (item != NULL)
677691
{
678-
memFree(memory, sizeof(TxItem) + item->datagram_payload.size, item);
692+
if (item->datagram_payload.data != NULL)
693+
{
694+
memFree(memory.payload, item->datagram_payload.size, item->datagram_payload.data);
695+
}
696+
697+
memFree(memory.fragment, sizeof(TxItem), item);
679698
}
680699
}
681700

libudpard/udpard.h

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,28 @@ struct UdpardMemoryResource
373373
// ================================================= TX PIPELINE =================================================
374374
// =====================================================================================================================
375375

376+
/// The set of memory resources is used per an TX pipeline instance.
377+
/// These are used to serve the memory needs of the library to keep state while assembling outgoing frames.
378+
/// Several memory resources are provided to enable fine control over the allocated memory.
379+
///
380+
/// This TX queue uses these memory resources for allocating the enqueued items (UDP datagrams).
381+
/// There are exactly two allocations per enqueued item:
382+
/// - the first for bookkeeping purposes (UdpardTxItem)
383+
/// - second for payload storage (the frame data)
384+
/// In a simple application, there would be just one memory resource shared by all parts of the library.
385+
/// If the application knows its MTU, it can use block allocation to avoid extrinsic fragmentation.
386+
///
387+
struct UdpardTxMemoryResources
388+
{
389+
/// The fragment handles are allocated per payload fragment; each handle contains a pointer to its fragment.
390+
/// Each instance is of a very small fixed size, so a trivial zero-fragmentation block allocator is enough.
391+
struct UdpardMemoryResource fragment;
392+
393+
/// The payload fragments are allocated per payload frame; each payload fragment is at most MTU-sized buffer,
394+
/// so a trivial zero-fragmentation MTU-sized block allocator is enough if MTU is known in advance.
395+
struct UdpardMemoryResource payload;
396+
};
397+
376398
/// The transmission pipeline is a prioritized transmission queue that keeps UDP datagrams (aka transport frames)
377399
/// destined for transmission via one network interface.
378400
/// Applications with redundant network interfaces are expected to have one instance of this type per interface.
@@ -424,12 +446,8 @@ struct UdpardTx
424446
/// The value can be changed arbitrarily at any time between enqueue operations.
425447
uint_least8_t dscp_value_per_priority[UDPARD_PRIORITY_MAX + 1U];
426448

427-
/// The memory resource used by this queue for allocating the enqueued items (UDP datagrams).
428-
/// There is exactly one allocation per enqueued item, each allocation contains both the UdpardTxItem
429-
/// and its payload, hence the size is variable.
430-
/// In a simple application there would be just one memory resource shared by all parts of the library.
431-
/// If the application knows its MTU, it can use block allocation to avoid extrinsic fragmentation.
432-
struct UdpardMemoryResource memory;
449+
/// Refer to UdpardTxMemoryResources.
450+
struct UdpardTxMemoryResources memory;
433451

434452
/// The number of frames that are currently contained in the queue, initially zero.
435453
/// READ-ONLY
@@ -490,10 +508,10 @@ struct UdpardTxItem
490508
///
491509
/// The return value is zero on success, otherwise it is a negative error code.
492510
/// The time complexity is constant. This function does not invoke the dynamic memory manager.
493-
int_fast8_t udpardTxInit(struct UdpardTx* const self,
494-
const UdpardNodeID* const local_node_id,
495-
const size_t queue_capacity,
496-
const struct UdpardMemoryResource memory);
511+
int_fast8_t udpardTxInit(struct UdpardTx* const self,
512+
const UdpardNodeID* const local_node_id,
513+
const size_t queue_capacity,
514+
const struct UdpardTxMemoryResources memory);
497515

498516
/// This function serializes a message transfer into a sequence of UDP datagrams and inserts them into the prioritized
499517
/// transmission queue at the appropriate position. Afterwards, the application is supposed to take the enqueued frames
@@ -613,35 +631,33 @@ int32_t udpardTxRespond(struct UdpardTx* const self,
613631
///
614632
/// If the queue is non-empty, the returned value is a pointer to its top element (i.e., the next item to transmit).
615633
/// The returned pointer points to an object allocated in the dynamic storage; it should be eventually freed by the
616-
/// application by calling udpardTxFree with UdpardTx::memory. The memory shall not be freed before the item is removed
634+
/// application by calling `udpardTxFree`. The memory shall not be freed before the item is removed
617635
/// from the queue by calling udpardTxPop; this is because until udpardTxPop is executed, the library retains
618636
/// ownership of the item. The pointer retains validity until explicitly freed by the application; in other words,
619637
/// calling udpardTxPop does not invalidate the object.
620638
///
621-
/// The payload buffer is located shortly after the object itself, in the same memory fragment. The application shall
622-
/// not attempt to free it.
623-
///
624639
/// Calling functions that modify the queue may cause the next invocation to return a different pointer.
625640
///
626641
/// The time complexity is logarithmic of the queue size. This function does not invoke the dynamic memory manager.
627-
const struct UdpardTxItem* udpardTxPeek(const struct UdpardTx* const self);
642+
struct UdpardTxItem* udpardTxPeek(const struct UdpardTx* const self);
628643

629644
/// This function transfers the ownership of the specified item of the prioritized transmission queue from the queue
630645
/// to the application. The item does not necessarily need to be the top one -- it is safe to dequeue any item.
631646
/// The item is dequeued but not invalidated; it is the responsibility of the application to deallocate its memory
632-
/// later. The memory SHALL NOT be deallocated UNTIL this function is invoked.
647+
/// later. The memory SHALL NOT be deallocated UNTIL this function is invoked (use `udpardTxFree` helper).
633648
/// The function returns the same pointer that it is given except that it becomes mutable.
634649
///
635650
/// If any of the arguments are NULL, the function has no effect and returns NULL.
636651
///
637652
/// The time complexity is logarithmic of the queue size. This function does not invoke the dynamic memory manager.
638653
struct UdpardTxItem* udpardTxPop(struct UdpardTx* const self, const struct UdpardTxItem* const item);
639654

640-
/// This is a simple helper that frees the memory allocated for the item with the correct size.
641-
/// It is needed because the application does not have access to the required context to compute the size.
642-
/// If the chosen allocator does not leverage the size information, the deallocation function can be invoked directly.
655+
/// This is a simple helper that frees the memory allocated for the item and its payload,
656+
/// using the correct sizes and memory resources.
643657
/// If the item argument is NULL, the function has no effect. The time complexity is constant.
644-
void udpardTxFree(const struct UdpardMemoryResource memory, struct UdpardTxItem* const item);
658+
/// If the item frame payload is NULL then it is assumed that the payload buffer was already freed,
659+
/// or moved to a different owner (f.e. to media layer).
660+
void udpardTxFree(const struct UdpardTxMemoryResources memory, struct UdpardTxItem* const item);
645661

646662
// =====================================================================================================================
647663
// ================================================= RX PIPELINE =================================================

0 commit comments

Comments
 (0)