@@ -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
6876typedef 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.
11921200typedef 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.
12161231typedef 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
12461261static 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
12541266static 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 ));
0 commit comments