33
44#include " helpers.h"
55#include < unity.h>
6+ #include < array>
67#include < cstdint>
78#include < cstdlib>
89#include < memory>
@@ -45,6 +46,62 @@ static const canard_mem_vtable_t kStdMemVtable = {
4546 .alloc = std_alloc_mem,
4647};
4748
49+ // Captures outgoing TX callback invocations for API-level poll/unicast checks.
50+ struct tx_record_t
51+ {
52+ canard_us_t deadline;
53+ uint_least8_t iface_index;
54+ bool fd;
55+ uint32_t can_id;
56+ uint_least8_t tail;
57+ };
58+
59+ struct tx_capture_t
60+ {
61+ canard_us_t now;
62+ bool accept_tx;
63+ size_t count;
64+ std::array<tx_record_t , 32 > records;
65+ };
66+
67+ static tx_capture_t * capture_from (canard_t * const self) { return static_cast <tx_capture_t *>(self->user_context ); }
68+
69+ static canard_us_t capture_now (canard_t * const self) { return capture_from (self)->now ; }
70+
71+ static bool capture_tx (canard_t * const self,
72+ const canard_user_context_t ,
73+ const canard_us_t deadline,
74+ const uint_least8_t iface_index,
75+ const bool fd,
76+ const uint32_t extended_can_id,
77+ const canard_bytes_t can_data)
78+ {
79+ tx_capture_t * const cap = capture_from (self);
80+ TEST_ASSERT_NOT_NULL (cap);
81+ if (cap->count < cap->records .size ()) {
82+ cap->records [cap->count ] = tx_record_t {
83+ .deadline = deadline,
84+ .iface_index = iface_index,
85+ .fd = fd,
86+ .can_id = extended_can_id,
87+ .tail = 0U ,
88+ };
89+ if ((can_data.size > 0U ) && (can_data.data != nullptr )) {
90+ const auto * const bytes = static_cast <const uint_least8_t *>(can_data.data );
91+ cap->records [cap->count ].tail = bytes[can_data.size - 1U ];
92+ }
93+ }
94+ cap->count ++;
95+ return cap->accept_tx ;
96+ }
97+
98+ static const canard_vtable_t kCaptureVtable = {
99+ .now = capture_now,
100+ .on_p2p = nullptr ,
101+ .tx = capture_tx,
102+ .filter = mock_filter,
103+ };
104+
48105static canard_mem_set_t make_std_memory ()
49106{
50107 const canard_mem_t r = { .vtable = &kStdMemVtable , .context = nullptr };
@@ -56,6 +113,16 @@ static canard_mem_set_t make_std_memory()
56113 };
57114}
58115
116+ static void init_with_capture (canard_t * const self, tx_capture_t * const capture)
117+ {
118+ *capture = tx_capture_t {};
119+ capture->now = 0 ;
120+ capture->accept_tx = true ;
121+ capture->count = 0 ;
122+ TEST_ASSERT_TRUE (canard_new (self, &kCaptureVtable , make_std_memory (), 16U , 42U , 1234U , 0U , nullptr ));
123+ self->user_context = capture;
124+ }
125+
59126// Basic constructor argument validation.
60127static void test_canard_new_validation ()
61128{
@@ -216,6 +283,129 @@ static void test_canard_publish_subject_id_out_of_range()
216283 &self, 0 , 1 , canard_prio_nominal, CANARD_SUBJECT_ID_MAX + 1U , 0 , payload, CANARD_USER_CONTEXT_NULL));
217284}
218285
286+ // Poll only drives interfaces marked writable in the provided bitmap.
287+ static void test_canard_poll_ready_bitmap ()
288+ {
289+ canard_t self = {};
290+ tx_capture_t cap = {};
291+ init_with_capture (&self, &cap);
292+
293+ const canard_bytes_chain_t payload = { .bytes = { .size = 0 , .data = nullptr }, .next = nullptr };
294+ TEST_ASSERT_TRUE (canard_publish (&self, 1000 , 3U , canard_prio_nominal, 10U , 0U , payload, CANARD_USER_CONTEXT_NULL));
295+
296+ canard_poll (&self, 1U );
297+ TEST_ASSERT_EQUAL_size_t (1U , cap.count );
298+ TEST_ASSERT_EQUAL_UINT8 (0U , cap.records [0 ].iface_index );
299+ TEST_ASSERT_EQUAL_UINT8 (2U , canard_pending_ifaces (&self));
300+
301+ canard_poll (&self, 1U );
302+ TEST_ASSERT_EQUAL_size_t (1U , cap.count );
303+ TEST_ASSERT_EQUAL_UINT8 (2U , canard_pending_ifaces (&self));
304+
305+ canard_poll (&self, 2U );
306+ TEST_ASSERT_EQUAL_size_t (2U , cap.count );
307+ TEST_ASSERT_EQUAL_UINT8 (1U , cap.records [1 ].iface_index );
308+ TEST_ASSERT_EQUAL_UINT8 (0U , canard_pending_ifaces (&self));
309+
310+ canard_destroy (&self);
311+ }
312+
313+ // Poll keeps pending frames if TX callback reports temporary backpressure.
314+ static void test_canard_poll_backpressure ()
315+ {
316+ canard_t self = {};
317+ tx_capture_t cap = {};
318+ init_with_capture (&self, &cap);
319+
320+ const canard_bytes_chain_t payload = { .bytes = { .size = 0 , .data = nullptr }, .next = nullptr };
321+ TEST_ASSERT_TRUE (canard_publish (&self, 1000 , 1U , canard_prio_nominal, 10U , 0U , payload, CANARD_USER_CONTEXT_NULL));
322+
323+ cap.accept_tx = false ;
324+ canard_poll (&self, 1U );
325+ TEST_ASSERT_EQUAL_size_t (1U , cap.count );
326+ TEST_ASSERT_EQUAL_UINT8 (1U , canard_pending_ifaces (&self));
327+
328+ if (cap.count > 0U ) {
329+ cap.accept_tx = true ;
330+ }
331+ canard_poll (&self, 1U );
332+ TEST_ASSERT_EQUAL_size_t (2U , cap.count );
333+ TEST_ASSERT_EQUAL_UINT8 (0U , canard_pending_ifaces (&self));
334+
335+ canard_destroy (&self);
336+ }
337+
338+ // Poll retires expired transfers before attempting to transmit.
339+ static void test_canard_poll_expiration ()
340+ {
341+ canard_t self = {};
342+ tx_capture_t cap = {};
343+ init_with_capture (&self, &cap);
344+
345+ const canard_bytes_chain_t payload = { .bytes = { .size = 0 , .data = nullptr }, .next = nullptr };
346+ TEST_ASSERT_TRUE (canard_publish (&self, 10 , 1U , canard_prio_nominal, 10U , 0U , payload, CANARD_USER_CONTEXT_NULL));
347+
348+ cap.now = 11 ;
349+ canard_poll (&self, 1U );
350+ TEST_ASSERT_EQUAL_size_t (0U , cap.count );
351+ TEST_ASSERT_EQUAL_UINT8 (0U , canard_pending_ifaces (&self));
352+ TEST_ASSERT_EQUAL_UINT64 (1U , self.err .tx_expiration );
353+
354+ canard_destroy (&self);
355+ }
356+
357+ // Validate unicast argument checking.
358+ static void test_canard_unicast_validation ()
359+ {
360+ canard_t self = {};
361+ tx_capture_t cap = {};
362+ init_with_capture (&self, &cap);
363+
364+ const canard_bytes_chain_t payload = { .bytes = { .size = 0 , .data = nullptr }, .next = nullptr };
365+ TEST_ASSERT_FALSE (canard_unicast (nullptr , 0 , 1U , canard_prio_nominal, payload, CANARD_USER_CONTEXT_NULL));
366+ TEST_ASSERT_FALSE (
367+ canard_unicast (&self, 0 , CANARD_NODE_ID_MAX + 1U , canard_prio_nominal, payload, CANARD_USER_CONTEXT_NULL));
368+ const canard_bytes_chain_t bad_payload = { .bytes = { .size = 1 , .data = nullptr }, .next = nullptr };
369+ TEST_ASSERT_FALSE (canard_unicast (&self, 0 , 1U , canard_prio_nominal, bad_payload, CANARD_USER_CONTEXT_NULL));
370+
371+ canard_destroy (&self);
372+ }
373+
374+ // Validate unicast CAN-ID encoding and per-destination transfer-ID tracking.
375+ static void test_canard_unicast_encoding_and_transfer_id ()
376+ {
377+ canard_t self = {};
378+ tx_capture_t cap = {};
379+ init_with_capture (&self, &cap);
380+
381+ self.unicast_transfer_id [10 ] = CANARD_TRANSFER_ID_MAX;
382+ const canard_bytes_chain_t payload = { .bytes = { .size = 0 , .data = nullptr }, .next = nullptr };
383+ TEST_ASSERT_TRUE (canard_unicast (&self, 1000 , 10U , canard_prio_high, payload, CANARD_USER_CONTEXT_NULL));
384+ TEST_ASSERT_TRUE (canard_unicast (&self, 1000 , 10U , canard_prio_high, payload, CANARD_USER_CONTEXT_NULL));
385+ TEST_ASSERT_TRUE (canard_unicast (&self, 1000 , 11U , canard_prio_high, payload, CANARD_USER_CONTEXT_NULL));
386+ TEST_ASSERT_EQUAL_UINT8 (1U , self.unicast_transfer_id [10 ] & CANARD_TRANSFER_ID_MAX);
387+ TEST_ASSERT_EQUAL_UINT8 (1U , self.unicast_transfer_id [11 ]);
388+
389+ canard_poll (&self, 1U );
390+ canard_poll (&self, 1U );
391+ canard_poll (&self, 1U );
392+ TEST_ASSERT_EQUAL_size_t (3U , cap.count );
393+
394+ const uint_least8_t expected_dest[] = { 10U , 10U , 11U };
395+ const uint_least8_t expected_tid[] = { CANARD_TRANSFER_ID_MAX, 0U , 0U };
396+ for (size_t i = 0 ; i < 3U ; i++) {
397+ const uint32_t can_id = cap.records [i].can_id ;
398+ TEST_ASSERT_EQUAL_UINT8 ((uint8_t )canard_prio_high, (uint8_t )((can_id >> 26U ) & 7U ));
399+ TEST_ASSERT_EQUAL_UINT32 (CANARD_SERVICE_ID_UNICAST, (can_id >> 14U ) & CANARD_SERVICE_ID_MAX);
400+ TEST_ASSERT_EQUAL_UINT8 (1U , (uint8_t )((can_id >> 23U ) & 1U ));
401+ TEST_ASSERT_EQUAL_UINT8 (expected_dest[i], (uint8_t )((can_id >> 7U ) & CANARD_NODE_ID_MAX));
402+ TEST_ASSERT_EQUAL_UINT8 (42U , (uint8_t )(can_id & CANARD_NODE_ID_MAX));
403+ TEST_ASSERT_EQUAL_UINT8 (expected_tid[i], (uint8_t )(cap.records [i].tail & CANARD_TRANSFER_ID_MAX));
404+ }
405+
406+ canard_destroy (&self);
407+ }
408+
219409extern " C" void setUp () {}
220410extern " C" void tearDown () {}
221411
@@ -234,6 +424,11 @@ int main()
234424 RUN_TEST (test_canard_publish_oom);
235425 RUN_TEST (test_canard_0v1_publish_requires_node_id);
236426 RUN_TEST (test_canard_publish_subject_id_out_of_range);
427+ RUN_TEST (test_canard_poll_ready_bitmap);
428+ RUN_TEST (test_canard_poll_backpressure);
429+ RUN_TEST (test_canard_poll_expiration);
430+ RUN_TEST (test_canard_unicast_validation);
431+ RUN_TEST (test_canard_unicast_encoding_and_transfer_id);
237432
238433 return UNITY_END ();
239434}
0 commit comments