Skip to content

Commit 9d538c6

Browse files
implement canard_ingest_frame()
1 parent a0ab028 commit 9d538c6

File tree

6 files changed

+172
-122
lines changed

6 files changed

+172
-122
lines changed

.idea/dictionaries/project.xml

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

libcanard/canard.c

Lines changed: 128 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1108,11 +1108,11 @@ static byte_t rx_parse(const uint32_t can_id,
11081108
const bool svc = (can_id & (UINT32_C(1) << 25U)) != 0U;
11091109
const bool bit_23 = (can_id & (UINT32_C(1) << 23U)) != 0U;
11101110
if (svc) {
1111-
is_v1 = is_v1 && !bit_23;
11121111
out_v1->dst = (byte_t)((can_id >> 7U) & CANARD_NODE_ID_MAX);
11131112
out_v1->port_id = (can_id >> 14U) & CANARD_SERVICE_ID_MAX;
11141113
const bool req = (can_id & (UINT32_C(1) << 24U)) != 0U;
11151114
out_v1->kind = req ? canard_kind_1v0_request : canard_kind_1v0_response;
1115+
is_v1 = is_v1 && !bit_23 && (out_v1->src != out_v1->dst); // self-addressing not allowed
11161116
} else {
11171117
out_v1->dst = CANARD_NODE_ID_ANONYMOUS;
11181118
const bool is_1v1 = (can_id & (UINT32_C(1) << 7U)) != 0U;
@@ -1141,11 +1141,12 @@ static byte_t rx_parse(const uint32_t can_id,
11411141
const bool svc = (can_id & (UINT32_C(1) << 7U)) != 0U;
11421142
if (svc) {
11431143
const byte_t dst = (byte_t)((can_id >> 8U) & CANARD_NODE_ID_MAX);
1144-
is_v0 = is_v0 && (dst != 0) && (src != 0); // 0 reserved for anonymous/broadcast, not for svc.
11451144
out_v0->dst = dst;
11461145
out_v0->port_id = (can_id >> 16U) & 0xFFU;
11471146
const bool req = (can_id & (UINT32_C(1) << 15U)) != 0U;
11481147
out_v0->kind = req ? canard_kind_0v1_request : canard_kind_0v1_response;
1148+
// Node-ID 0 reserved for anonymous/broadcast, invalid for services. Self-addressing not allowed.
1149+
is_v0 = is_v0 && (dst != 0) && (src != 0) && (src != dst);
11491150
} else {
11501151
out_v0->dst = CANARD_NODE_ID_ANONYMOUS;
11511152
out_v0->port_id = (can_id >> 8U) & 0xFFFFU;
@@ -1449,10 +1450,9 @@ static bool rx_session_solve_admission(const rx_session_t* const ses,
14491450
return (fresh && affine) || (affine && stale) || (stale && fresh);
14501451
}
14511452

1452-
// Returns false on OOM, no other failure modes.
14531453
// The caller must ensure the frame is of the correct version that matches the subscription (v0/v1).
14541454
// The caller must ensure the frame is directed to the local node (broadcast or unicast to the local node-ID).
1455-
static bool rx_session_update(canard_subscription_t* const sub,
1455+
static void rx_session_update(canard_subscription_t* const sub,
14561456
const canard_us_t ts,
14571457
const frame_t* const frame,
14581458
const byte_t iface_index)
@@ -1477,14 +1477,14 @@ static bool rx_session_update(canard_subscription_t* const sub,
14771477
index);
14781478
if (ses == NULL) {
14791479
sub->owner->err.oom += frame->start;
1480-
return !frame->start;
1480+
return;
14811481
}
14821482

14831483
// Decide admit or drop.
14841484
const bool admit = rx_session_solve_admission(
14851485
ses, ts, frame->priority, frame->start, frame->toggle, frame->transfer_id, iface_index);
14861486
if (!admit) {
1487-
return true; // Rejection is not a failure.
1487+
return;
14881488
}
14891489

14901490
// The frame must be accepted. If this is the start of a new transfer, we must update state.
@@ -1502,7 +1502,7 @@ static bool rx_session_update(canard_subscription_t* const sub,
15021502
ses->slots[frame->priority] = rx_slot_new(sub, ts, frame->transfer_id, iface_index);
15031503
if (ses->slots[frame->priority] == NULL) {
15041504
sub->owner->err.oom++;
1505-
return false;
1505+
return;
15061506
}
15071507
CANARD_ASSERT(ses->slots[frame->priority]->transfer_id == frame->transfer_id);
15081508
CANARD_ASSERT(ses->slots[frame->priority]->expected_toggle == frame->toggle);
@@ -1514,35 +1514,94 @@ static bool rx_session_update(canard_subscription_t* const sub,
15141514
// Accept the frame.
15151515
rx_session_accept(ses, ts, frame);
15161516
CANARD_ASSERT(!frame->end || (ses->slots[frame->priority] == NULL));
1517-
return true;
1517+
}
1518+
1519+
static int32_t rx_subscription_cavl_compare(const void* const user, const canard_tree_t* const node)
1520+
{
1521+
return ((int32_t)(*(uint32_t*)user)) - ((int32_t)((canard_subscription_t*)node)->port_id);
1522+
}
1523+
1524+
// Locates the appropriate subscription if the destination is matching and there is a subscription.
1525+
static canard_subscription_t* rx_route(const canard_t* const self, const frame_t* const fr)
1526+
{
1527+
CANARD_ASSERT((self != NULL) && (fr != NULL));
1528+
if ((fr->dst != CANARD_NODE_ID_ANONYMOUS) && (fr->dst != self->node_id)) {
1529+
return NULL; // misfiltered
1530+
}
1531+
return (canard_subscription_t*)cavl2_find(
1532+
self->rx.subscriptions[fr->kind], &fr->port_id, rx_subscription_cavl_compare);
1533+
}
1534+
1535+
// Recompute the filter configuration and apply.
1536+
// Must be invoked after modification of the subscription set and after the local node-ID is changed.
1537+
static void rx_filter_configure(const canard_t* const self)
1538+
{
1539+
if (self->vtable->filter == NULL) {
1540+
return; // No filtering support, nothing to do.
1541+
}
1542+
(void)self;
1543+
// TODO not implemented.
15181544
}
15191545

15201546
// --------------------------------------------- MISC ---------------------------------------------
15211547

1548+
// The splitmix64 PRNG algorithm. Original work by Sebastiano Vigna, released under CC0-1.0 (public domain dedication).
1549+
// Source http://xoshiro.di.unimi.it/splitmix64.c.
1550+
static uint64_t splitmix64(uint64_t* const state)
1551+
{
1552+
uint64_t z = (*state += 0x9E3779B97F4A7C15ULL);
1553+
z = (z ^ (z >> 30U)) * 0xBF58476D1CE4E5B9ULL;
1554+
z = (z ^ (z >> 27U)) * 0x94D049BB133111EBULL;
1555+
return z ^ (z >> 31U);
1556+
}
1557+
1558+
// Obtains a decent-quality pseudo-random number in [0, cardinality).
1559+
static uint64_t random(canard_t* const self, const uint64_t cardinality)
1560+
{
1561+
CANARD_ASSERT(cardinality > 0);
1562+
return splitmix64(&self->prng_state) % cardinality;
1563+
}
1564+
1565+
static void node_id_occupancy_reset(canard_t* const self)
1566+
{
1567+
self->node_id_occupancy_bitmap[0] = 1; // Reserve 0 for compatibility with v0
1568+
self->node_id_occupancy_bitmap[1] = 0;
1569+
}
1570+
1571+
// Records the seen node-ID and reallocates the local node if a collision is found.
1572+
static void node_id_occupancy_update(canard_t* const self, const byte_t src, const byte_t dst)
1573+
{
1574+
(void)self;
1575+
(void)src;
1576+
(void)dst;
1577+
// TODO not implemented.
1578+
// TODO filtering.
1579+
rx_filter_configure(self);
1580+
}
1581+
15221582
bool canard_new(canard_t* const self,
15231583
const canard_vtable_t* const vtable,
15241584
const canard_mem_set_t memory,
15251585
const size_t tx_queue_capacity,
1526-
const uint_least8_t node_id,
15271586
const uint64_t prng_seed,
15281587
const size_t filter_count,
15291588
canard_filter_t* const filter_storage)
15301589
{
15311590
const bool ok = (self != NULL) && (vtable != NULL) && (vtable->now != NULL) && (vtable->tx != NULL) &&
1532-
(vtable->filter != NULL) && mem_valid(memory.tx_transfer) && mem_valid(memory.tx_frame) &&
1533-
mem_valid(memory.rx_session) && mem_valid(memory.rx_payload) &&
1534-
((filter_count == 0U) || (filter_storage != NULL)) && (node_id <= CANARD_NODE_ID_MAX);
1591+
mem_valid(memory.tx_transfer) && mem_valid(memory.tx_frame) && mem_valid(memory.rx_session) &&
1592+
mem_valid(memory.rx_payload) && ((filter_count == 0U) || (filter_storage != NULL));
15351593
if (ok) {
15361594
(void)memset(self, 0, sizeof(*self));
1537-
self->node_id = (node_id <= CANARD_NODE_ID_MAX) ? node_id : CANARD_NODE_ID_ANONYMOUS;
15381595
self->tx.fd = true;
15391596
self->tx.queue_capacity = tx_queue_capacity;
15401597
self->rx.filter_count = filter_count;
15411598
self->rx.filters = filter_storage;
15421599
self->mem = memory;
1543-
self->prng_state = prng_seed;
1600+
self->prng_state = prng_seed ^ (uintptr_t)self;
15441601
self->vtable = vtable;
15451602
self->unicast_sub.index_port_id = TREE_NULL;
1603+
self->node_id = (byte_t)(random(self, CANARD_NODE_ID_MAX) + 1U); // [1, 127]
1604+
node_id_occupancy_reset(self);
15461605
}
15471606
return ok;
15481607
}
@@ -1563,11 +1622,21 @@ void canard_destroy(canard_t* const self)
15631622
(void)memset(self, 0, sizeof(*self)); // UAF safety
15641623
}
15651624

1625+
bool canard_set_node_id(canard_t* const self, const uint_least8_t node_id)
1626+
{
1627+
const bool ok = (self != NULL) && (node_id <= CANARD_NODE_ID_MAX);
1628+
if (ok && (node_id != self->node_id)) {
1629+
self->node_id = node_id;
1630+
node_id_occupancy_reset(self);
1631+
rx_filter_configure(self);
1632+
}
1633+
return ok;
1634+
}
1635+
15661636
void canard_poll(canard_t* const self, const uint_least8_t tx_ready_iface_bitmap)
15671637
{
15681638
if (self != NULL) {
15691639
const canard_us_t now = self->vtable->now(self);
1570-
15711640
// Drop stale sessions to reclaim memory. This happens when remote peers cease sending data.
15721641
// The oldest is held alive until its session timeout has expired, but notice that it may be different
15731642
// depending on the subscription instance if large transfer-ID values are used.
@@ -1583,7 +1652,6 @@ void canard_poll(canard_t* const self, const uint_least8_t tx_ready_iface_bitmap
15831652
rx_session_destroy(ses);
15841653
}
15851654
}
1586-
15871655
// Process the TX pipeline.
15881656
tx_expire(self, now); // deadline maintenance first to keep queue pressure bounded
15891657
FOREACH_IFACE (i) { // submit queued frames through all currently writable interfaces
@@ -1594,19 +1662,50 @@ void canard_poll(canard_t* const self, const uint_least8_t tx_ready_iface_bitmap
15941662
}
15951663
}
15961664

1597-
// bool canard_ingest_frame(canard_t* const self,
1598-
// const canard_us_t timestamp,
1599-
// const uint_least8_t iface_index,
1600-
// const uint32_t extended_can_id,
1601-
// const canard_bytes_t can_data)
1602-
// {
1603-
// bool ok = (self != NULL) && (timestamp >= 0) && (iface_index < CANARD_IFACE_COUNT) &&
1604-
// (extended_can_id < (UINT32_C(1) << 29U)) && ((can_data.size == 0) || (can_data.data != NULL));
1605-
// if (ok) {
1606-
// ok = false; // TODO
1607-
// }
1608-
// return ok;
1609-
// }
1665+
static void ingest_frame(canard_t* const self,
1666+
const canard_us_t timestamp,
1667+
const uint_least8_t iface_index,
1668+
const frame_t frame)
1669+
{
1670+
// Update the node-ID occupancy/collision monitoring states before routing the message.
1671+
// We do this only on start frames because non-start frames have the version detection ambiguity,
1672+
// which is not a problem for the routing logic (new states can only be created on start frames),
1673+
// but it may cause phantom occupancy detection. Also, doing it only on start frames reduces the load.
1674+
if (frame.start) {
1675+
node_id_occupancy_update(self, frame.src, frame.dst);
1676+
}
1677+
// Route the frame to the appropriate destination internally.
1678+
canard_subscription_t* const sub = rx_route(self, &frame);
1679+
if (sub != NULL) {
1680+
rx_session_update(sub, timestamp, &frame, iface_index);
1681+
}
1682+
}
1683+
1684+
bool canard_ingest_frame(canard_t* const self,
1685+
const canard_us_t timestamp,
1686+
const uint_least8_t iface_index,
1687+
const uint32_t extended_can_id,
1688+
const canard_bytes_t can_data)
1689+
{
1690+
const bool ok = (self != NULL) && (timestamp >= 0) && (iface_index < CANARD_IFACE_COUNT) &&
1691+
(extended_can_id < (UINT32_C(1) << 29U)) && ((can_data.size == 0) || (can_data.data != NULL));
1692+
if (ok) {
1693+
frame_t frs[2] = { { 0 }, { 0 } };
1694+
const byte_t parsed = rx_parse(extended_can_id, can_data, &frs[0], &frs[1]);
1695+
if (parsed == 0) {
1696+
self->err.rx_frame++;
1697+
}
1698+
if ((parsed & 1U) != 0) {
1699+
CANARD_ASSERT(canard_kind_version(frs[0].kind) == 0);
1700+
ingest_frame(self, timestamp, iface_index, frs[0]);
1701+
}
1702+
if ((parsed & 2U) != 0) {
1703+
CANARD_ASSERT(canard_kind_version(frs[1].kind) == 1);
1704+
ingest_frame(self, timestamp, iface_index, frs[1]);
1705+
}
1706+
}
1707+
return ok;
1708+
}
16101709

16111710
uint16_t canard_0v1_crc_seed_from_data_type_signature(const uint64_t data_type_signature)
16121711
{

libcanard/canard.h

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -294,17 +294,21 @@ typedef struct canard_vtable_t
294294
/// Reconfigure the acceptance filters of the CAN controller hardware.
295295
/// The prior configuration, if any, is replaced entirely.
296296
/// filter_count is guaranteed to not exceed the value given at initialization.
297-
/// Returns true on success, false if the filters could not be applied; another attempt will be made later.
298-
bool (*filter)(canard_t*, size_t filter_count, const canard_filter_t* filters);
297+
/// This function may be NULL if the CAN controller/driver does not support filtering or it is not desired.
298+
/// The implementation is assumed to be infallible; if error handling is necessary, it must be implemented
299+
/// on the application side, perhaps with retries.
300+
void (*filter)(canard_t*, size_t filter_count, const canard_filter_t* filters);
299301
} canard_vtable_t;
300302

301303
/// None of the fields should be mutated by the application, unless explicitly allowed.
302304
struct canard_t
303305
{
304-
/// If automatic allocation is used, libcanard will avoid picking a node-ID of zero to ensure compatibility with
305-
/// legacy v0 nodes, where node-ID zero is reserved for anonymous nodes. Zero can be assigned manually,
306-
/// but it is only a good idea in networks where no legacy v0 nodes are present.
307-
uint64_t node_id_occupancy_bitmap[2];
306+
uint64_t node_id_occupancy_bitmap[2];
307+
308+
/// By default, the node-ID is allocated automatically, with occupancy/collision tracking.
309+
/// Automatic allocation will avoid using the node-ID of zero to ensure compatibility with legacy v0 nodes, where
310+
/// zero is reserved for anonymous nodes. Zero can still be assigned manually if compatibility is not needed.
311+
/// The node-ID can be set manually via the corresponding function.
308312
uint_least8_t node_id;
309313

310314
struct
@@ -356,6 +360,7 @@ struct canard_t
356360
uint64_t tx_expiration; ///< A transfer had to be dequeued due to deadline expiration.
357361
uint64_t rx_frame; ///< A received frame was malformed and thus dropped.
358362
uint64_t rx_transfer; ///< A transfer could not be reassembled correctly.
363+
uint64_t collision; ///< Number of times the local node-ID was changed to repair a collision.
359364
} err;
360365

361366
canard_mem_set_t mem;
@@ -379,13 +384,9 @@ struct canard_t
379384
/// In the absence of a true RNG, a good way to obtain the seed is to use a unique hardware identifier hashed
380385
/// down to 64 bits with a good hash, e.g., rapidhash.
381386
///
382-
/// The node_id parameter should be CANARD_NODE_ID_ANONYMOUS to enable automatic stateless allocation;
383-
/// however, if the application has a preferred node-ID (e.g., restored from a non-volatile memory),
384-
/// it may be passed here directly. Regardless of whether it is assigned automatically or manually,
385-
/// if another node with the same node-ID is detected, a re-allocation will be done automatically.
386-
/// Even if the node-ID is allocated automatically, it is recommended to save it in non-volatile memory for
387-
/// faster startup next time and to avoid the risk of unnecessary perturbations to the network.
388-
/// The same node-ID is used for both v1 and legacy v0 communications.
387+
/// The local node-ID will be chosen randomly by default, and the stack will monitor the network for node-ID occupancy
388+
/// and collisions, and will automatically migrate to a free node-ID shall a collision be detected.
389+
/// If manual allocation is desired, use the corresponding function to set the node-ID after initialization.
389390
///
390391
/// The filter storage is an array of filters that is used by the library to automatically set up the acceptance
391392
/// filters when the RX pipeline is reconfigured. The filter count equals the storage size. The storage must
@@ -398,7 +399,6 @@ bool canard_new(canard_t* const self,
398399
const canard_vtable_t* const vtable,
399400
const canard_mem_set_t memory,
400401
const size_t tx_queue_capacity,
401-
const uint_least8_t node_id,
402402
const uint64_t prng_seed,
403403
const size_t filter_count,
404404
canard_filter_t* const filter_storage);
@@ -407,6 +407,10 @@ bool canard_new(canard_t* const self,
407407
/// The TX queue will be purged automatically if not empty.
408408
void canard_destroy(canard_t* const self);
409409

410+
/// This can be invoked after initialization to manually assign the desired node-ID.
411+
/// This does not disable the occupancy/collision monitoring; the assigned ID will be changed if a collision is found.
412+
bool canard_set_node_id(canard_t* const self, const uint_least8_t node_id);
413+
410414
/// This must be invoked periodically to ensure liveliness.
411415
/// The function must be called asap once any of the interfaces for which there are pending outgoing transfers
412416
/// become writable, and not less frequently than once in a few milliseconds. The invocation rate defines the
@@ -417,8 +421,8 @@ void canard_poll(canard_t* const self, const uint_least8_t tx_ready_iface_bitmap
417421
uint_least8_t canard_pending_ifaces(const canard_t* const self);
418422

419423
/// True if successfully processed, false if any of the arguments are invalid.
420-
/// A malformed frame is not considered an error; it is simply dropped and the corresponding counter is incremented.
421-
/// The can_data is copied and thus can be discarded by the caller after this function returns.
424+
/// Other failures are reported via the counters.
425+
/// The lifetime of can_data can end after this function returns.
422426
bool canard_ingest_frame(canard_t* const self,
423427
const canard_us_t timestamp,
424428
const uint_least8_t iface_index,

0 commit comments

Comments
 (0)