Skip to content

Commit 8546527

Browse files
advance
1 parent 6fb9c90 commit 8546527

File tree

2 files changed

+288
-0
lines changed

2 files changed

+288
-0
lines changed

libcanard/canard.c

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -771,6 +771,66 @@ static bool tx_push(canard_t* const self,
771771
return true;
772772
}
773773

774+
static canard_txfer_t* tx_pending_node_to_transfer(const canard_tree_t* const node, const byte_t iface_index)
775+
{
776+
return (canard_txfer_t*)ptr_unbias(
777+
node, offsetof(canard_txfer_t, index_pending) + (((size_t)iface_index) * sizeof(canard_tree_t)));
778+
}
779+
780+
static void tx_expire_iterative(canard_t* const self, const canard_us_t now)
781+
{
782+
if (self->tx.iter == NULL) {
783+
self->tx.iter = LIST_HEAD(self->tx.agewise, canard_txfer_t, list_agewise);
784+
}
785+
if (self->tx.iter != NULL) {
786+
canard_txfer_t* const tr = self->tx.iter;
787+
self->tx.iter = LIST_NEXT(tr, canard_txfer_t, list_agewise);
788+
if (now > tr->deadline) {
789+
txfer_retire(self, tr);
790+
self->err.tx_expiration++;
791+
}
792+
}
793+
}
794+
795+
static void tx_eject_pending(canard_t* const self, const byte_t iface_index)
796+
{
797+
while (true) {
798+
const canard_tree_t* const pending = cavl2_min(self->tx.pending[iface_index]);
799+
if (pending == NULL) {
800+
break;
801+
}
802+
canard_txfer_t* const tr = tx_pending_node_to_transfer(pending, iface_index);
803+
CANARD_ASSERT((tr->head[iface_index] != NULL) && (tr->cursor[iface_index] != NULL));
804+
805+
// Try to eject one frame.
806+
tx_frame_t* const frame = tr->cursor[iface_index];
807+
tx_frame_t* const frame_next = frame->next;
808+
const bool ejected = self->vtable->tx(self,
809+
tr->user_context,
810+
tr->deadline,
811+
iface_index,
812+
tr->fd != 0U,
813+
(((uint32_t)tr->can_id_msb) << 7U) | self->node_id,
814+
tx_frame_view(frame));
815+
if (!ejected) {
816+
break;
817+
}
818+
819+
// Commit successful ejection by advancing this interface cursor.
820+
tr->head[iface_index] = frame_next;
821+
tr->cursor[iface_index] = frame_next;
822+
canard_refcount_dec(self, tx_frame_view(frame));
823+
824+
// If this interface is done with the transfer, remove it from this pending tree.
825+
if (frame_next == NULL) {
826+
(void)cavl2_remove_if(&self->tx.pending[iface_index], &tr->index_pending[iface_index]);
827+
if (!txfer_is_pending(self, tr)) {
828+
txfer_retire(self, tr);
829+
}
830+
}
831+
}
832+
}
833+
774834
bool canard_new(canard_t* const self,
775835
const canard_vtable_t* const vtable,
776836
const canard_mem_set_t memory,
@@ -815,6 +875,18 @@ void canard_destroy(canard_t* const self)
815875
(void)memset(self, 0, sizeof(*self));
816876
}
817877

878+
void canard_poll(canard_t* const self, const uint_least8_t tx_ready_iface_bitmap)
879+
{
880+
if (self != NULL) {
881+
tx_expire_iterative(self, self->vtable->now(self)); // deadline maintenance first to keep queue pressure bounded
882+
FOREACH_IFACE (i) { // submit queued frames through all currently writable interfaces
883+
if ((tx_ready_iface_bitmap & (1U << i)) != 0U) {
884+
tx_eject_pending(self, (byte_t)i);
885+
}
886+
}
887+
}
888+
}
889+
818890
uint_least8_t canard_pending_ifaces(const canard_t* const self)
819891
{
820892
uint_least8_t out = 0;
@@ -847,6 +919,27 @@ bool canard_publish(canard_t* const self,
847919
return ok;
848920
}
849921

922+
bool canard_unicast(canard_t* const self,
923+
const canard_us_t deadline,
924+
const uint_least8_t destination_node_id,
925+
const canard_prio_t priority,
926+
const canard_bytes_chain_t payload,
927+
const canard_user_context_t context)
928+
{
929+
bool ok = (self != NULL) && (priority < CANARD_PRIO_COUNT) && bytes_chain_valid(payload) &&
930+
(destination_node_id <= CANARD_NODE_ID_MAX);
931+
if (ok) {
932+
const byte_t tr_id = (byte_t)self->unicast_transfer_id[destination_node_id]++;
933+
const uint32_t can_id = (((uint32_t)priority) << PRIO_SHIFT) |
934+
(((uint32_t)CANARD_SERVICE_ID_UNICAST) << 14U) | //
935+
(1UL << 23U) | // request
936+
(((uint32_t)destination_node_id) << 7U);
937+
canard_txfer_t* const tr = txfer_new(self, deadline, tr_id, can_id, self->tx.fd, context);
938+
ok = (tr != NULL) && tx_push(self, tr, false, CANARD_IFACE_BITMAP_ALL, payload, CRC_INITIAL);
939+
}
940+
return ok;
941+
}
942+
850943
bool canard_1v0_publish(canard_t* const self,
851944
const canard_us_t deadline,
852945
const uint_least8_t iface_bitmap,

tests/src/test_api_tx.cpp

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
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+
48105
static 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.
60127
static 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]);
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+
219409
extern "C" void setUp() {}
220410
extern "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

Comments
 (0)