@@ -87,6 +87,8 @@ static void test_mem_endpoint_list_guards(void)
8787 udpard_listed_t tail = { 0 };
8888 enlist_head (& list , & tail );
8989 TEST_ASSERT_TRUE (is_listed (& list , & member ));
90+ // is_listed returns true when next is populated.
91+ TEST_ASSERT_TRUE (is_listed (& list , & tail ));
9092
9193 // NULL endpoint list yields empty bitmap.
9294 TEST_ASSERT_EQUAL_UINT16 (0U , valid_ep_bitmap (NULL ));
@@ -158,12 +160,19 @@ static void test_tx_guards(void)
158160 // Push helpers reject invalid timing and null handles.
159161 const uint16_t iface_bitmap_1 = (1U << 0U );
160162 const udpard_bytes_scattered_t empty_payload = { .bytes = { .size = 0U , .data = NULL }, .next = NULL };
163+ const udpard_remote_t remote_ok = { .uid = 1 , .endpoints = { { .ip = 1U , .port = UDP_PORT } } };
161164 TEST_ASSERT_FALSE (
162165 udpard_tx_push (& tx , 10 , 5 , iface_bitmap_1 , udpard_prio_fast , 1U , empty_payload , NULL , UDPARD_USER_CONTEXT_NULL ));
163166 TEST_ASSERT_FALSE (
164167 udpard_tx_push (NULL , 0 , 0 , iface_bitmap_1 , udpard_prio_fast , 1U , empty_payload , NULL , UDPARD_USER_CONTEXT_NULL ));
165- TEST_ASSERT_FALSE (udpard_tx_push_p2p (
166- NULL , 0 , 0 , udpard_prio_fast , (udpard_remote_t ){ 0 }, empty_payload , NULL , UDPARD_USER_CONTEXT_NULL , NULL ));
168+ TEST_ASSERT_FALSE (
169+ udpard_tx_push_p2p (NULL , 0 , 0 , udpard_prio_fast , remote_ok , empty_payload , NULL , UDPARD_USER_CONTEXT_NULL , NULL ));
170+ // P2P pushes reject expired deadlines.
171+ TEST_ASSERT_FALSE (
172+ udpard_tx_push_p2p (& tx , 2 , 1 , udpard_prio_fast , remote_ok , empty_payload , NULL , UDPARD_USER_CONTEXT_NULL , NULL ));
173+ // P2P pushes reject negative timestamps.
174+ TEST_ASSERT_FALSE (
175+ udpard_tx_push_p2p (& tx , -1 , 0 , udpard_prio_fast , remote_ok , empty_payload , NULL , UDPARD_USER_CONTEXT_NULL , NULL ));
167176 // Reject invalid payload pointer and empty interface bitmap.
168177 const udpard_bytes_scattered_t bad_payload = { .bytes = { .size = 1U , .data = NULL }, .next = NULL };
169178 TEST_ASSERT_FALSE (
@@ -174,6 +183,34 @@ static void test_tx_guards(void)
174183 TEST_ASSERT_FALSE (
175184 udpard_tx_push_p2p (& tx , 0 , 1 , udpard_prio_fast , remote_bad , empty_payload , NULL , UDPARD_USER_CONTEXT_NULL , NULL ));
176185
186+ // Reject invalid timestamps and priority.
187+ TEST_ASSERT_FALSE (
188+ udpard_tx_push (& tx , -1 , 0 , iface_bitmap_1 , udpard_prio_fast , 1U , empty_payload , NULL , UDPARD_USER_CONTEXT_NULL ));
189+ // Use an out-of-range priority without a constant enum cast.
190+ udpard_prio_t bad_prio = udpard_prio_optional ;
191+ const unsigned bad_prio_raw = UDPARD_PRIORITY_COUNT ;
192+ memcpy (& bad_prio , & bad_prio_raw , sizeof (bad_prio ));
193+ TEST_ASSERT_FALSE (
194+ udpard_tx_push (& tx , 0 , 1 , iface_bitmap_1 , bad_prio , 1U , empty_payload , NULL , UDPARD_USER_CONTEXT_NULL ));
195+
196+ // Reject zero local UID.
197+ const uint64_t saved_uid = tx .local_uid ;
198+ tx .local_uid = 0U ;
199+ TEST_ASSERT_FALSE (
200+ udpard_tx_push (& tx , 0 , 1 , iface_bitmap_1 , udpard_prio_fast , 1U , empty_payload , NULL , UDPARD_USER_CONTEXT_NULL ));
201+ tx .local_uid = saved_uid ;
202+
203+ // P2P guard paths cover local UID, priority, and payload pointer.
204+ uint64_t out_tid = 0 ;
205+ tx .local_uid = 0U ;
206+ TEST_ASSERT_FALSE (udpard_tx_push_p2p (
207+ & tx , 0 , 1 , udpard_prio_fast , remote_ok , empty_payload , NULL , UDPARD_USER_CONTEXT_NULL , & out_tid ));
208+ tx .local_uid = saved_uid ;
209+ TEST_ASSERT_FALSE (
210+ udpard_tx_push_p2p (& tx , 0 , 1 , bad_prio , remote_ok , empty_payload , NULL , UDPARD_USER_CONTEXT_NULL , & out_tid ));
211+ TEST_ASSERT_FALSE (udpard_tx_push_p2p (
212+ & tx , 0 , 1 , udpard_prio_fast , remote_ok , bad_payload , NULL , UDPARD_USER_CONTEXT_NULL , & out_tid ));
213+
177214 // Poll and refcount no-ops on null data.
178215 udpard_tx_poll (NULL , 0 , 0 );
179216 udpard_tx_poll (& tx , (udpard_us_t )- 1 , 0 );
@@ -199,6 +236,20 @@ static void test_tx_predictor_sharing(void)
199236 make_mem (& shared_tag [1 ]),
200237 make_mem (& shared_tag [1 ]) };
201238 TEST_ASSERT_EQUAL_size_t (2U , tx_predict_frame_count (mtu , mem_arr_split , iface_bitmap_12 , 16U ));
239+
240+ // Shared spool when payload fits smaller MTU despite mismatch.
241+ const size_t mtu_mixed [UDPARD_IFACE_COUNT_MAX ] = { 64U , 128U , 128U };
242+ const uint16_t iface_bitmap_01 = (1U << 0U ) | (1U << 1U );
243+ TEST_ASSERT_EQUAL_size_t (1U , tx_predict_frame_count (mtu_mixed , mem_arr , iface_bitmap_01 , 32U ));
244+
245+ // Gapped bitmap exercises the unset-bit branch.
246+ static char gap_tag [3 ];
247+ const udpard_mem_t mem_gap [UDPARD_IFACE_COUNT_MAX ] = { make_mem (& gap_tag [0 ]),
248+ make_mem (& gap_tag [1 ]),
249+ make_mem (& gap_tag [2 ]) };
250+ const size_t mtu_gap [UDPARD_IFACE_COUNT_MAX ] = { 64U , 64U , 64U };
251+ const uint16_t iface_bitmap_02 = (1U << 0U ) | (1U << 2U );
252+ TEST_ASSERT_EQUAL_size_t (2U , tx_predict_frame_count (mtu_gap , mem_gap , iface_bitmap_02 , 16U ));
202253}
203254
204255static void test_rx_guards (void )
@@ -229,7 +280,11 @@ static void test_rx_guards(void)
229280 TEST_ASSERT_FALSE (rx_validate_mem_resources (bad_fragment ));
230281 bad_fragment .fragment .vtable = & vtable_no_alloc ;
231282 TEST_ASSERT_FALSE (rx_validate_mem_resources (bad_fragment ));
283+ bad_fragment .fragment .vtable = NULL ;
284+ TEST_ASSERT_FALSE (rx_validate_mem_resources (bad_fragment ));
232285 TEST_ASSERT_TRUE (udpard_rx_port_new_stateless (& port , 8U , rx_mem , & rx_vtb ));
286+ TEST_ASSERT_FALSE (udpard_rx_port_new_stateless (& port , 8U , bad_fragment , & rx_vtb ));
287+ TEST_ASSERT_FALSE (udpard_rx_port_new_p2p (& port , 8U , bad_fragment , & rx_vtb ));
233288
234289 // Invalid datagram inputs are rejected without processing.
235290 udpard_rx_t rx ;
@@ -250,6 +305,59 @@ static void test_rx_guards(void)
250305 small_payload ,
251306 (udpard_deleter_t ){ .vtable = & (udpard_deleter_vtable_t ){ .free = NULL }, .context = NULL },
252307 0 ));
308+ // Cover each guard term with a valid baseline payload.
309+ const udpard_deleter_t deleter_ok = { .vtable = & deleter_vtable , .context = NULL };
310+ byte_t dgram [HEADER_SIZE_BYTES ];
311+ const meta_t meta = { .priority = udpard_prio_nominal ,
312+ .kind = frame_msg_best ,
313+ .transfer_payload_size = 0 ,
314+ .transfer_id = 1 ,
315+ .sender_uid = 2 };
316+ header_serialize (dgram , meta , 0 , 0 , crc_full (0 , NULL ));
317+ const udpard_bytes_mut_t dgram_view = { .size = sizeof (dgram ), .data = dgram };
318+ const udpard_udpip_ep_t ep_ok = { .ip = 1U , .port = UDP_PORT };
319+ TEST_ASSERT_FALSE (udpard_rx_port_push (NULL , & port , 0 , ep_ok , dgram_view , deleter_ok , 0 ));
320+ TEST_ASSERT_FALSE (udpard_rx_port_push (& rx , NULL , 0 , ep_ok , dgram_view , deleter_ok , 0 ));
321+ TEST_ASSERT_FALSE (udpard_rx_port_push (& rx , & port , -1 , ep_ok , dgram_view , deleter_ok , 0 ));
322+ TEST_ASSERT_FALSE (
323+ udpard_rx_port_push (& rx , & port , 0 , (udpard_udpip_ep_t ){ .ip = 0U , .port = UDP_PORT }, dgram_view , deleter_ok , 0 ));
324+ TEST_ASSERT_FALSE (
325+ udpard_rx_port_push (& rx , & port , 0 , ep_ok , (udpard_bytes_mut_t ){ .size = 1U , .data = NULL }, deleter_ok , 0 ));
326+ TEST_ASSERT_FALSE (udpard_rx_port_push (& rx , & port , 0 , ep_ok , dgram_view , deleter_ok , UDPARD_IFACE_COUNT_MAX ));
327+ TEST_ASSERT_FALSE (
328+ udpard_rx_port_push (& rx , & port , 0 , ep_ok , dgram_view , (udpard_deleter_t ){ .vtable = NULL , .context = NULL }, 0 ));
329+ TEST_ASSERT_FALSE (
330+ udpard_rx_port_push (& rx ,
331+ & port ,
332+ 0 ,
333+ ep_ok ,
334+ dgram_view ,
335+ (udpard_deleter_t ){ .vtable = & (udpard_deleter_vtable_t ){ .free = NULL }, .context = NULL },
336+ 0 ));
337+
338+ // ACK frames are accepted on P2P ports.
339+ udpard_rx_port_t port_p2p ;
340+ TEST_ASSERT_TRUE (udpard_rx_port_new_p2p (& port_p2p , 8U , rx_mem , & rx_vtb ));
341+ const meta_t ack_meta = { .priority = udpard_prio_nominal ,
342+ .kind = frame_ack ,
343+ .transfer_payload_size = 0 ,
344+ .transfer_id = 2 ,
345+ .sender_uid = 3 };
346+ header_serialize (dgram , ack_meta , 0 , 0 , crc_full (0 , NULL ));
347+ TEST_ASSERT_TRUE (udpard_rx_port_push (& rx , & port_p2p , 0 , ep_ok , dgram_view , deleter_ok , 0 ));
348+
349+ // ACK frames are rejected on non-P2P ports.
350+ const uint64_t errors_before_ack = rx .errors_frame_malformed ;
351+ header_serialize (dgram , ack_meta , 0 , 0 , crc_full (0 , NULL ));
352+ TEST_ASSERT_TRUE (udpard_rx_port_push (& rx , & port , 0 , ep_ok , dgram_view , deleter_ok , 0 ));
353+ TEST_ASSERT_EQUAL_UINT64 (errors_before_ack + 1U , rx .errors_frame_malformed );
354+
355+ // Malformed frames are rejected after parsing.
356+ const uint64_t errors_before_bad = rx .errors_frame_malformed ;
357+ header_serialize (dgram , meta , 0 , 0 , crc_full (0 , NULL ));
358+ dgram [HEADER_SIZE_BYTES - 1 ] ^= 0xFFU ;
359+ TEST_ASSERT_TRUE (udpard_rx_port_push (& rx , & port , 0 , ep_ok , dgram_view , deleter_ok , 0 ));
360+ TEST_ASSERT_EQUAL_UINT64 (errors_before_bad + 1U , rx .errors_frame_malformed );
253361
254362 // Port freeing should tolerate null rx.
255363 udpard_rx_port_free (NULL , & port );
0 commit comments