Skip to content

Commit 84b984f

Browse files
Allow changing cy_subscription_t::extent at any time
1 parent 6efdd09 commit 84b984f

5 files changed

Lines changed: 399 additions & 15 deletions

File tree

.clang-tidy

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ Checks: >-
3636
-*-avoid-c-arrays,
3737
-*-nested-conditional-operator,
3838
-*DeprecatedOrUnsafeBufferHandling,
39+
-*-implicit-bool-conversion,
3940
CheckOptions:
4041
- key: readability-function-cognitive-complexity.Threshold
4142
value: '99'

libcanard/canard.c

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1190,6 +1190,7 @@ static byte_t rx_parse(const uint32_t can_id,
11901190
typedef struct
11911191
{
11921192
canard_us_t start_ts;
1193+
size_t extent; // Captured from sub->extent at slot creation time.
11931194
uint32_t total_size; // The raw payload size seen before the implicit truncation and CRC removal.
11941195
uint16_t crc;
11951196
byte_t transfer_id : CANARD_TRANSFER_ID_BITS;
@@ -1198,7 +1199,6 @@ typedef struct
11981199
byte_t payload[]; // Extent-sized.
11991200
} rx_slot_t;
12001201
#define RX_SLOT_OVERHEAD (offsetof(rx_slot_t, payload))
1201-
static_assert(RX_SLOT_OVERHEAD <= 16, "unexpected layout");
12021202

12031203
static rx_slot_t* rx_slot_new(const canard_subscription_t* const sub,
12041204
const canard_us_t start_ts,
@@ -1209,6 +1209,7 @@ static rx_slot_t* rx_slot_new(const canard_subscription_t* const sub,
12091209
if (slot != NULL) {
12101210
memset(slot, 0, RX_SLOT_OVERHEAD);
12111211
slot->start_ts = start_ts;
1212+
slot->extent = sub->extent;
12121213
slot->crc = sub->crc_seed;
12131214
slot->transfer_id = transfer_id & CANARD_TRANSFER_ID_MAX;
12141215
slot->expected_toggle = canard_kind_version(sub->kind) & 1U;
@@ -1219,13 +1220,15 @@ static rx_slot_t* rx_slot_new(const canard_subscription_t* const sub,
12191220

12201221
static void rx_slot_destroy(const canard_subscription_t* const sub, rx_slot_t* const slot)
12211222
{
1222-
mem_free(sub->owner->mem.rx_payload, RX_SLOT_OVERHEAD + sub->extent, slot);
1223+
if (slot != NULL) {
1224+
mem_free(sub->owner->mem.rx_payload, RX_SLOT_OVERHEAD + slot->extent, slot);
1225+
}
12231226
}
12241227

1225-
static void rx_slot_advance(rx_slot_t* const slot, const size_t extent, const canard_bytes_t payload)
1228+
static void rx_slot_advance(rx_slot_t* const slot, const canard_bytes_t payload)
12261229
{
1227-
if (slot->total_size < extent) {
1228-
const size_t copy_size = smaller(payload.size, (size_t)(extent - slot->total_size)); // NOLINT(*-casting)
1230+
if (slot->total_size < slot->extent) {
1231+
const size_t copy_size = smaller(payload.size, (size_t)(slot->extent - slot->total_size)); // NOLINT(*-casting)
12291232
(void)memcpy(&slot->payload[slot->total_size], payload.data, copy_size);
12301233
}
12311234
slot->total_size = (uint32_t)(slot->total_size + payload.size); // Before truncation.
@@ -1358,12 +1361,12 @@ static void rx_session_complete_slot(rx_session_t* const ses, const frame_t* con
13581361
ses->slots[fr->priority] = NULL; // Slot memory ownership transferred to the application, or destroyed.
13591362
const bool v1 = canard_kind_version(sub->kind) == 1;
13601363
const uint16_t crc_ref = v1 ? CRC_RESIDUE : (uint16_t)(slot->payload[0] | (((unsigned)slot->payload[1]) << 8U));
1361-
CANARD_ASSERT(v1 || (sub->extent >= CRC_BYTES)); // In v0, the CRC size is included in the extent.
1364+
CANARD_ASSERT(v1 || (slot->extent >= CRC_BYTES)); // In v0, the CRC size is included in the extent.
13621365
if (slot->crc == crc_ref) {
1363-
const size_t size = smaller(slot->total_size - CRC_BYTES, sub->extent - (v1 ? 0 : CRC_BYTES));
1366+
const size_t size = smaller(slot->total_size - CRC_BYTES, slot->extent - (v1 ? 0 : CRC_BYTES));
13641367
const canard_payload_t payload = {
13651368
.view = { .data = v1 ? slot->payload : &slot->payload[CRC_BYTES], .size = size },
1366-
.origin = { .data = slot, .size = RX_SLOT_OVERHEAD + sub->extent },
1369+
.origin = { .data = slot, .size = RX_SLOT_OVERHEAD + slot->extent },
13671370
};
13681371
sub->vtable->on_message(sub, slot->start_ts, fr->priority, fr->src, fr->transfer_id, payload);
13691372
} else {
@@ -1380,7 +1383,7 @@ static void rx_session_accept(rx_session_t* const ses, const canard_us_t ts_fram
13801383
if (slot != NULL) {
13811384
CANARD_ASSERT((!fr->start || !fr->end) && (slot->expected_toggle == fr->toggle));
13821385
CANARD_ASSERT((slot->transfer_id == fr->transfer_id) && (slot->iface_index == ses->iface_index));
1383-
rx_slot_advance(slot, sub->extent, fr->payload);
1386+
rx_slot_advance(slot, fr->payload);
13841387
// Multi-frame transfers place CRC differently in v1 and v0.
13851388
// The v1 handling is trivial: simply compute the full payload CRC and ensure the residue is correct.
13861389
// The payload may be truncated to the subscription extent, but the CRC is computed over the full payload.

libcanard/canard.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,13 +238,13 @@ struct canard_subscription_vtable_t
238238
/// Subscription instances must not be moved while in use.
239239
/// Each subscription is indexed by its port-ID inside the canard instance, and in turn contains a tree of sessions
240240
/// indexed by remote node-ID. Two log-time lookups are thus required to handle an incoming frame.
241-
/// None of the fields may be mutated by the application after initialization except for the user context.
241+
/// None of the fields may be mutated by the application after initialization except for the user context and extent.
242242
struct canard_subscription_t
243243
{
244244
canard_tree_t index_port_id; ///< Must be the first member.
245245

246246
canard_us_t transfer_id_timeout;
247-
size_t extent; ///< Must not be altered after initialization! In v0 includes the CRC.
247+
size_t extent; ///< May be changed at any time, takes effect with next transfer. In v0 must include CRC!
248248
uint16_t port_id; ///< Represents subjects, services, and legacy message- and service data type IDs.
249249
uint16_t crc_seed; ///< For v0 this is set at subscription time, for v1 this is always 0xFFFF.
250250
canard_kind_t kind;

tests/src/test_api_rx_edge.cpp

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1301,6 +1301,192 @@ static void test_rx_v0_extent_truncation()
13011301
canard_destroy(&self);
13021302
}
13031303

1304+
// ------------------------------------------- Dynamic extent
1305+
// ----------------------------------------------------------
1306+
1307+
/// Change extent between two complete multiframe transfers. The second transfer must use the new extent.
1308+
static void test_rx_extent_change_between_transfers()
1309+
{
1310+
canard_t self = {};
1311+
canard_us_t now_val = 0;
1312+
init_canard(&self, &now_val, 42U);
1313+
1314+
rx_capture_t cap = {};
1315+
canard_subscription_t sub = {};
1316+
TEST_ASSERT_TRUE(canard_subscribe_16b(&self, &sub, 1500U, 5U, 2000000, &capture_sub_vtable));
1317+
sub.user_context = &cap;
1318+
1319+
const uint_least8_t payload[8] = { 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7 };
1320+
const uint32_t can_id = make_v1v1_msg_can_id(canard_prio_nominal, 1500U, 10U);
1321+
1322+
// First transfer with extent=5. CRC over 8 payload + 4 padding.
1323+
const uint_least8_t pad[4] = {};
1324+
uint16_t crc = crc16_ccitt(0xFFFFU, payload, 8U);
1325+
crc = crc16_ccitt(crc, pad, 4U);
1326+
const auto crc_hi = static_cast<uint_least8_t>((static_cast<unsigned>(crc) >> 8U) & 0xFFU);
1327+
const auto crc_lo = static_cast<uint_least8_t>(crc & 0xFFU);
1328+
1329+
uint_least8_t frame1[8];
1330+
std::memcpy(frame1, payload, 7U);
1331+
frame1[7] = make_v1_tail(true, false, true, 2U);
1332+
1333+
uint_least8_t frame2[8];
1334+
frame2[0] = payload[7];
1335+
frame2[1] = 0x00;
1336+
frame2[2] = 0x00;
1337+
frame2[3] = 0x00;
1338+
frame2[4] = 0x00;
1339+
frame2[5] = crc_hi;
1340+
frame2[6] = crc_lo;
1341+
frame2[7] = make_v1_tail(false, true, false, 2U);
1342+
1343+
now_val = 100;
1344+
TEST_ASSERT_TRUE(
1345+
canard_ingest_frame(&self, 100, 0U, can_id, canard_bytes_t{ .size = sizeof(frame1), .data = frame1 }));
1346+
TEST_ASSERT_TRUE(
1347+
canard_ingest_frame(&self, 200, 0U, can_id, canard_bytes_t{ .size = sizeof(frame2), .data = frame2 }));
1348+
TEST_ASSERT_EQUAL_size_t(1U, cap.count);
1349+
TEST_ASSERT_EQUAL_size_t(5U, cap.payload_size); // Truncated to extent=5.
1350+
1351+
// Change extent to 100 and send a second transfer (TID=3).
1352+
sub.extent = 100;
1353+
cap = {};
1354+
1355+
uint_least8_t frame3[8];
1356+
std::memcpy(frame3, payload, 7U);
1357+
frame3[7] = make_v1_tail(true, false, true, 3U);
1358+
1359+
uint_least8_t frame4[8];
1360+
frame4[0] = payload[7];
1361+
frame4[1] = 0x00;
1362+
frame4[2] = 0x00;
1363+
frame4[3] = 0x00;
1364+
frame4[4] = 0x00;
1365+
frame4[5] = crc_hi; // Same CRC: same payload+padding.
1366+
frame4[6] = crc_lo;
1367+
frame4[7] = make_v1_tail(false, true, false, 3U);
1368+
1369+
now_val = 1000;
1370+
TEST_ASSERT_TRUE(
1371+
canard_ingest_frame(&self, 1000, 0U, can_id, canard_bytes_t{ .size = sizeof(frame3), .data = frame3 }));
1372+
TEST_ASSERT_TRUE(
1373+
canard_ingest_frame(&self, 1100, 0U, can_id, canard_bytes_t{ .size = sizeof(frame4), .data = frame4 }));
1374+
TEST_ASSERT_EQUAL_size_t(1U, cap.count);
1375+
// With extent=100, all 12 bytes (8 payload + 4 padding) are delivered: min(14-2, 100) = 12.
1376+
TEST_ASSERT_EQUAL_size_t(12U, cap.payload_size);
1377+
TEST_ASSERT_EQUAL_UINT8_ARRAY(payload, cap.payload_buf, 8U);
1378+
1379+
canard_unsubscribe(&self, &sub);
1380+
canard_destroy(&self);
1381+
}
1382+
1383+
/// Shrink extent while a multiframe transfer is in flight. The in-flight slot must use the original extent.
1384+
static void test_rx_extent_shrink_during_inflight()
1385+
{
1386+
canard_t self = {};
1387+
canard_us_t now_val = 0;
1388+
init_canard(&self, &now_val, 42U);
1389+
1390+
rx_capture_t cap = {};
1391+
canard_subscription_t sub = {};
1392+
TEST_ASSERT_TRUE(canard_subscribe_16b(&self, &sub, 1600U, 100U, 2000000, &capture_sub_vtable));
1393+
sub.user_context = &cap;
1394+
1395+
const uint_least8_t payload[8] = { 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7 };
1396+
const uint32_t can_id = make_v1v1_msg_can_id(canard_prio_nominal, 1600U, 10U);
1397+
1398+
const uint_least8_t pad[4] = {};
1399+
uint16_t crc = crc16_ccitt(0xFFFFU, payload, 8U);
1400+
crc = crc16_ccitt(crc, pad, 4U);
1401+
const auto crc_hi = static_cast<uint_least8_t>((static_cast<unsigned>(crc) >> 8U) & 0xFFU);
1402+
const auto crc_lo = static_cast<uint_least8_t>(crc & 0xFFU);
1403+
1404+
uint_least8_t frame1[8];
1405+
std::memcpy(frame1, payload, 7U);
1406+
frame1[7] = make_v1_tail(true, false, true, 4U);
1407+
1408+
// Feed frame 1, then shrink extent.
1409+
now_val = 100;
1410+
TEST_ASSERT_TRUE(
1411+
canard_ingest_frame(&self, 100, 0U, can_id, canard_bytes_t{ .size = sizeof(frame1), .data = frame1 }));
1412+
1413+
sub.extent = 4; // Shrink from 100 to 4.
1414+
1415+
uint_least8_t frame2[8];
1416+
frame2[0] = payload[7];
1417+
frame2[1] = 0x00;
1418+
frame2[2] = 0x00;
1419+
frame2[3] = 0x00;
1420+
frame2[4] = 0x00;
1421+
frame2[5] = crc_hi;
1422+
frame2[6] = crc_lo;
1423+
frame2[7] = make_v1_tail(false, true, false, 4U);
1424+
1425+
TEST_ASSERT_TRUE(
1426+
canard_ingest_frame(&self, 200, 0U, can_id, canard_bytes_t{ .size = sizeof(frame2), .data = frame2 }));
1427+
TEST_ASSERT_EQUAL_size_t(1U, cap.count);
1428+
// The slot was allocated with extent=100. Truncation: min(14-2, 100) = 12.
1429+
TEST_ASSERT_EQUAL_size_t(12U, cap.payload_size);
1430+
TEST_ASSERT_EQUAL_UINT8_ARRAY(payload, cap.payload_buf, 8U);
1431+
1432+
canard_unsubscribe(&self, &sub);
1433+
canard_destroy(&self);
1434+
}
1435+
1436+
/// Grow extent while a multiframe transfer is in flight. The in-flight slot must use the original (smaller) extent.
1437+
static void test_rx_extent_grow_during_inflight()
1438+
{
1439+
canard_t self = {};
1440+
canard_us_t now_val = 0;
1441+
init_canard(&self, &now_val, 42U);
1442+
1443+
rx_capture_t cap = {};
1444+
canard_subscription_t sub = {};
1445+
TEST_ASSERT_TRUE(canard_subscribe_16b(&self, &sub, 1700U, 5U, 2000000, &capture_sub_vtable));
1446+
sub.user_context = &cap;
1447+
1448+
const uint_least8_t payload[8] = { 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7 };
1449+
const uint32_t can_id = make_v1v1_msg_can_id(canard_prio_nominal, 1700U, 10U);
1450+
1451+
const uint_least8_t pad[4] = {};
1452+
uint16_t crc = crc16_ccitt(0xFFFFU, payload, 8U);
1453+
crc = crc16_ccitt(crc, pad, 4U);
1454+
const auto crc_hi = static_cast<uint_least8_t>((static_cast<unsigned>(crc) >> 8U) & 0xFFU);
1455+
const auto crc_lo = static_cast<uint_least8_t>(crc & 0xFFU);
1456+
1457+
uint_least8_t frame1[8];
1458+
std::memcpy(frame1, payload, 7U);
1459+
frame1[7] = make_v1_tail(true, false, true, 5U);
1460+
1461+
// Feed frame 1, then grow extent.
1462+
now_val = 100;
1463+
TEST_ASSERT_TRUE(
1464+
canard_ingest_frame(&self, 100, 0U, can_id, canard_bytes_t{ .size = sizeof(frame1), .data = frame1 }));
1465+
1466+
sub.extent = 256; // Grow from 5 to 256.
1467+
1468+
uint_least8_t frame2[8];
1469+
frame2[0] = payload[7];
1470+
frame2[1] = 0x00;
1471+
frame2[2] = 0x00;
1472+
frame2[3] = 0x00;
1473+
frame2[4] = 0x00;
1474+
frame2[5] = crc_hi;
1475+
frame2[6] = crc_lo;
1476+
frame2[7] = make_v1_tail(false, true, false, 5U);
1477+
1478+
TEST_ASSERT_TRUE(
1479+
canard_ingest_frame(&self, 200, 0U, can_id, canard_bytes_t{ .size = sizeof(frame2), .data = frame2 }));
1480+
TEST_ASSERT_EQUAL_size_t(1U, cap.count);
1481+
// The slot was allocated with extent=5. Truncation: min(14-2, 5) = 5.
1482+
TEST_ASSERT_EQUAL_size_t(5U, cap.payload_size);
1483+
TEST_ASSERT_EQUAL_UINT8(0xC0, cap.payload_buf[0]);
1484+
TEST_ASSERT_EQUAL_UINT8(0xC4, cap.payload_buf[4]);
1485+
1486+
canard_unsubscribe(&self, &sub);
1487+
canard_destroy(&self);
1488+
}
1489+
13041490
// ------------------------------------------- Harness ---------------------------------------------------------------
13051491

13061492
extern "C" void setUp() {}
@@ -1354,5 +1540,10 @@ int main()
13541540
RUN_TEST(test_rx_v0_extent_excludes_crc);
13551541
RUN_TEST(test_rx_v0_extent_truncation);
13561542

1543+
// Dynamic extent.
1544+
RUN_TEST(test_rx_extent_change_between_transfers);
1545+
RUN_TEST(test_rx_extent_shrink_during_inflight);
1546+
RUN_TEST(test_rx_extent_grow_during_inflight);
1547+
13571548
return UNITY_END();
13581549
}

0 commit comments

Comments
 (0)