@@ -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 = ∩
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 = ∩
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 = ∩
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
13061492extern " 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