Skip to content

Commit fda9730

Browse files
simplify
1 parent dc47b3a commit fda9730

File tree

3 files changed

+44
-50
lines changed

3 files changed

+44
-50
lines changed

libcanard/canard.c

Lines changed: 40 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,14 @@ typedef unsigned char byte_t;
6363

6464
#define FOREACH_IFACE(i) for (size_t i = 0; (i) < CANARD_IFACE_COUNT; (i)++)
6565

66+
#if CANARD_IFACE_COUNT <= 4
67+
#define IFACE_INDEX_BIT_LENGTH 2U
68+
#elif CANARD_IFACE_COUNT <= 8
69+
#define IFACE_INDEX_BIT_LENGTH 3U
70+
#else
71+
#error "Too many interfaces"
72+
#endif
73+
6674
#define TREE_NULL (canard_tree_t){ NULL, { NULL, NULL }, 0 }
6775

6876
typedef enum format_t
@@ -1191,28 +1199,35 @@ static byte_t rx_transfer_id_forward_difference(const byte_t a, const byte_t b)
11911199
// Each state is only kept as long as the transfer reassembly is in progress; once it's completed, the slot is deleted.
11921200
typedef struct
11931201
{
1194-
canard_us_t start_ts;
1195-
size_t total_size; // The raw payload size before the implicit truncation and CRC removal.
1196-
canard_bytes_mut_t payload; // Dynamically allocated and handed off to the application when done.
1197-
uint16_t crc;
1198-
byte_t transfer_id : CANARD_TRANSFER_ID_BIT_LENGTH;
1199-
byte_t expected_toggle : 1;
1200-
byte_t iface_index;
1202+
canard_us_t start_ts;
1203+
size_t total_size; // The raw payload size before the implicit truncation and CRC removal.
1204+
uint16_t crc;
1205+
byte_t transfer_id : CANARD_TRANSFER_ID_BIT_LENGTH;
1206+
byte_t expected_toggle : 1;
1207+
byte_t iface_index : IFACE_INDEX_BIT_LENGTH;
1208+
byte_t payload[]; // Extent-sized.
12011209
} rx_slot_t;
1202-
static_assert((sizeof(void*) > 4) || (sizeof(rx_slot_t) <= 24), "too large");
1210+
#define RX_SLOT_OVERHEAD (offsetof(rx_slot_t, payload))
12031211

12041212
// Up to libcanard v4 we used a fixed-capacity array of pointers for per-remote sessions for constant-time lookup,
12051213
// but it was too costly on MCUs: with a 32-bit pointer it took 512 bytes for the array plus overheads,
12061214
// resulting in 1 KiB o1heap blocks per session, very expensive. Here we use a much less RAM-heavy approach with
12071215
// sparse nodes in a tree with log-time lookup.
12081216
//
1209-
// The session keeps the last received transfer-ID, plus each slot holds one as well. This is needed because a
1210-
// low-priority transfer may be temporarily preempted by a higher-priority one; each transfer has a unique transfer-ID
1211-
// within the transfer-ID wraparound window. It is possible that a low-priority transfer with ID N is preempted by a
1212-
// sequence of transfers whose count is a multiple of the wraparound window; if the transfer-ID deduplication was done
1213-
// at the slot level only, then such sequence could cause the slot to reject a new transfer as a duplicate. To avoid
1214-
// this, transfer-ID state in the slots is only used for start-of-transfer loss detection, while deduplication relies
1215-
// on the single shared state at the session level.
1217+
// The session keeps the last transfer-ID, plus each slot holds one as well. This is needed because a low-priority
1218+
// transfer may be temporarily preempted by a higher-priority one; each transfer has a unique transfer-ID within
1219+
// the transfer-ID wraparound window. It is possible that a low-priority transfer with ID N is preempted by a sequence
1220+
// of transfers whose count is a multiple of the wraparound window; if the transfer-ID deduplication was done at the
1221+
// slot level only, then such sequence could cause the slot to reject a new transfer as a duplicate. To avoid this,
1222+
// transfer-ID state in the slots is only used for start-of-transfer loss detection, while deduplication relies on the
1223+
// single shared state at the session level.
1224+
//
1225+
// Core invariants:
1226+
// - Only start-of-transfer may create/replace a slot.
1227+
// - Non-start frames never create state.
1228+
// - Session dedup state is updated on admitted start-of-transfer, not on transfer completion.
1229+
// - Timeout is consulted only for start-of-transfer admission.
1230+
// - Slot matching for continuation uses exact match: priority, transfer-ID, toggle, and iface.
12161231
typedef struct
12171232
{
12181233
canard_tree_t index;
@@ -1231,24 +1246,21 @@ static rx_slot_t* rx_slot_new(const canard_subscription_t* const sub,
12311246
const byte_t iface_index,
12321247
const bool v1)
12331248
{
1234-
rx_slot_t* const slot = mem_alloc_zero(sub->owner->mem.rx_slot, sizeof(rx_slot_t));
1249+
rx_slot_t* const slot = mem_alloc(sub->owner->mem.rx_payload, RX_SLOT_OVERHEAD + sub->extent);
12351250
if (slot != NULL) {
1251+
memset(slot, 0, RX_SLOT_OVERHEAD);
12361252
slot->start_ts = start_ts;
1237-
slot->payload.data = NULL;
12381253
slot->crc = sub->crc_seed;
12391254
slot->transfer_id = transfer_id & CANARD_TRANSFER_ID_MAX;
12401255
slot->expected_toggle = v1 ? 1 : 0;
1241-
slot->iface_index = iface_index;
1256+
slot->iface_index = iface_index & ((1U << IFACE_INDEX_BIT_LENGTH) - 1U);
12421257
}
12431258
return slot;
12441259
}
12451260

12461261
static void rx_slot_destroy(const canard_subscription_t* const sub, rx_slot_t* const slot)
12471262
{
1248-
if (slot != NULL) {
1249-
mem_free(sub->owner->mem.rx_payload, slot->payload.size, slot->payload.data);
1250-
mem_free(sub->owner->mem.rx_slot, sizeof(rx_slot_t), slot);
1251-
}
1263+
mem_free(sub->owner->mem.rx_payload, RX_SLOT_OVERHEAD + sub->extent, slot);
12521264
}
12531265

12541266
static int32_t rx_session_cavl_compare(const void* const user, const canard_tree_t* const node)
@@ -1314,29 +1326,13 @@ static size_t rx_session_scan(rx_session_t* const ses, const canard_us_t now)
13141326
return n_slots;
13151327
}
13161328

1317-
// Returns false on OOM, no other failure modes. Stores at most extent bytes. This is ONLY for multi-frame transfers.
1318-
static bool rx_slot_write_payload(rx_session_t* const ses, rx_slot_t* const slot, const canard_bytes_t payload)
1329+
static void rx_slot_write_payload(const rx_session_t* const ses, rx_slot_t* const slot, const canard_bytes_t payload)
13191330
{
1320-
CANARD_ASSERT(slot->payload.size <= ses->owner->extent); // enforced by the subscription logic
1321-
CANARD_ASSERT(slot->payload.size <= slot->total_size);
1322-
CANARD_ASSERT(payload.size > 0); // a multi-frame transfer cannot contain empty frames; enforced externally
1323-
const bool start = slot->total_size == 0;
1324-
slot->total_size += payload.size; // Before truncation.
1325-
if (ses->owner->extent == 0) {
1326-
return true;
1327-
}
1328-
if (start) {
1329-
CANARD_ASSERT((ses->owner->extent > 0) && (slot->payload.data == NULL));
1330-
slot->payload.data = mem_alloc(ses->owner->owner->mem.rx_payload, ses->owner->extent);
1331-
if (NULL == slot->payload.data) {
1332-
return false; // OOM. Must destroy the slot.
1333-
}
1331+
if (slot->total_size < ses->owner->extent) {
1332+
const size_t copy_size = smaller(payload.size, ses->owner->extent - slot->total_size);
1333+
(void)memcpy(&slot->payload[slot->total_size], payload.data, copy_size);
13341334
}
1335-
CANARD_ASSERT(slot->payload.data != NULL);
1336-
const size_t copy_size = smaller(payload.size, ses->owner->extent - slot->payload.size);
1337-
(void)memcpy(((byte_t*)slot->payload.data) + slot->payload.size, payload.data, copy_size);
1338-
slot->payload.size += copy_size;
1339-
return true;
1335+
slot->total_size += payload.size; // Before truncation.
13401336
}
13411337

13421338
// Returns false on OOM, no other failure modes.
@@ -1372,7 +1368,7 @@ bool canard_new(canard_t* const self,
13721368
{
13731369
bool ok = (self != NULL) && (vtable != NULL) && (vtable->now != NULL) && (vtable->tx != NULL) &&
13741370
(vtable->filter != NULL) && mem_valid(memory.tx_transfer) && mem_valid(memory.tx_frame) &&
1375-
mem_valid(memory.rx_session) && mem_valid(memory.rx_slot) && mem_valid(memory.rx_payload) &&
1371+
mem_valid(memory.rx_session) && mem_valid(memory.rx_payload) &&
13761372
((filter_count == 0U) || (filter_storage != NULL)) && (node_id <= CANARD_NODE_ID_MAX);
13771373
if (ok) {
13781374
(void)memset(self, 0, sizeof(*self));

libcanard/canard.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,8 +201,7 @@ typedef struct canard_mem_set_t
201201
canard_mem_t tx_transfer; ///< TX transfer objects, fixed-size, one per enqueued transfer.
202202
canard_mem_t tx_frame; ///< One per enqueued frame, at least one per TX transfer, size MTU+overhead.
203203
canard_mem_t rx_session; ///< Remote-associated sessions per subscriber, fixed-size.
204-
canard_mem_t rx_slot; ///< Reassembly slots per session, fixed-size.
205-
canard_mem_t rx_payload; ///< Variable-size, at most extent-sized.
204+
canard_mem_t rx_payload; ///< Variable-size, max size extent+sizeof(slot).
206205
} canard_mem_set_t;
207206

208207
typedef struct canard_subscription_t canard_subscription_t;

tests/src/test_api_tx.cpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ static const canard_vtable_t capture_vtable = {
100100
static canard_mem_set_t make_std_memory()
101101
{
102102
const canard_mem_t r = { .vtable = &std_mem_vtable, .context = nullptr };
103-
return canard_mem_set_t{ .tx_transfer = r, .tx_frame = r, .rx_session = r, .rx_slot = r, .rx_payload = r };
103+
return canard_mem_set_t{ .tx_transfer = r, .tx_frame = r, .rx_session = r, .rx_payload = r };
104104
}
105105

106106
static void init_with_capture_node_id(canard_t* const self, tx_capture_t* const capture, const uint_least8_t node_id)
@@ -126,9 +126,8 @@ static void test_canard_new_validation()
126126
const canard_mem_set_t mem = make_std_memory();
127127
const canard_mem_vtable_t bad_mv = { .free = std_free_mem, .alloc = nullptr };
128128
const canard_mem_t bad_mr = { .vtable = &bad_mv, .context = nullptr };
129-
const canard_mem_set_t bad_mem = canard_mem_set_t{
130-
.tx_transfer = bad_mr, .tx_frame = bad_mr, .rx_session = bad_mr, .rx_slot = bad_mr, .rx_payload = bad_mr
131-
};
129+
const canard_mem_set_t bad_mem =
130+
canard_mem_set_t{ .tx_transfer = bad_mr, .tx_frame = bad_mr, .rx_session = bad_mr, .rx_payload = bad_mr };
132131

133132
TEST_ASSERT_FALSE(canard_new(nullptr, &test_vtable, mem, 16, 1, 1234, 0, nullptr)); // Invalid self.
134133
TEST_ASSERT_FALSE(canard_new(&self, nullptr, mem, 16, 1, 1234, 0, nullptr)); // Invalid vtable.

0 commit comments

Comments
 (0)