@@ -331,3 +331,110 @@ TEST_CASE("RxSubscriptionErrors")
331
331
REQUIRE (-CANARD_ERROR_INVALID_ARGUMENT == canardRxAccept (&ins.getInstance (), 0 , &frame, 0 , nullptr , nullptr ));
332
332
REQUIRE (-CANARD_ERROR_INVALID_ARGUMENT == canardRxAccept (nullptr , 0 , nullptr , 0 , nullptr , nullptr ));
333
333
}
334
+
335
+ TEST_CASE (" Issue189" ) // https://github.com/OpenCyphal/libcanard/issues/189
336
+ {
337
+ using helpers::Instance;
338
+ using exposed::RxSession;
339
+
340
+ Instance ins;
341
+ CanardRxTransfer transfer{};
342
+ CanardRxSubscription* subscription = nullptr ;
343
+ const std::uint8_t redundant_transport_index = 0 ;
344
+
345
+ const auto accept = [&](const CanardMicrosecond timestamp_usec,
346
+ const std::uint32_t extended_can_id,
347
+ const std::vector<std::uint8_t >& payload) {
348
+ static std::vector<std::uint8_t > payload_storage;
349
+ payload_storage = payload;
350
+ CanardFrame frame{};
351
+ frame.extended_can_id = extended_can_id;
352
+ frame.payload_size = std::size (payload);
353
+ frame.payload = payload_storage.data ();
354
+ return ins.rxAccept (timestamp_usec, frame, redundant_transport_index, transfer, &subscription);
355
+ };
356
+
357
+ ins.getAllocator ().setAllocationCeiling (sizeof (RxSession) + 50 ); // A session and the payload buffer.
358
+
359
+ // Create a message subscription.
360
+ CanardRxSubscription sub_msg{};
361
+ REQUIRE (1 == ins.rxSubscribe (CanardTransferKindMessage, 0b0110011001100 , 50 , 1'000'000 , sub_msg));
362
+ REQUIRE (ins.getMessageSubs ().at (0 ) == &sub_msg);
363
+ REQUIRE (ins.getMessageSubs ().at (0 )->port_id == 0b0110011001100 );
364
+ REQUIRE (ins.getMessageSubs ().at (0 )->extent == 50 );
365
+ REQUIRE (ins.getMessageSubs ().at (0 )->transfer_id_timeout_usec == 1'000'000 );
366
+ REQUIRE (ensureAllNullptr (ins.getMessageSubs ().at (0 )->sessions ));
367
+ REQUIRE (ins.getResponseSubs ().empty ());
368
+ REQUIRE (ins.getRequestSubs ().empty ());
369
+
370
+ // First, ensure that the reassembler is initialized, by feeding it a valid transfer at least once.
371
+ subscription = nullptr ;
372
+ REQUIRE (1 == accept (100'000'001 , 0b001'00'0'11'0110011001100'0'0100111 , {0x42 , 0b111'00000 }));
373
+ REQUIRE (subscription != nullptr );
374
+ REQUIRE (subscription->port_id == 0b0110011001100 );
375
+ REQUIRE (transfer.timestamp_usec == 100'000'001 );
376
+ REQUIRE (transfer.metadata .priority == CanardPriorityImmediate);
377
+ REQUIRE (transfer.metadata .transfer_kind == CanardTransferKindMessage);
378
+ REQUIRE (transfer.metadata .port_id == 0b0110011001100 );
379
+ REQUIRE (transfer.metadata .remote_node_id == 0b0100111 );
380
+ REQUIRE (transfer.metadata .transfer_id == 0 );
381
+ REQUIRE (transfer.payload_size == 1 );
382
+ REQUIRE (0 == std::memcmp (transfer.payload , " \x42 " , 1 ));
383
+ REQUIRE (ins.getAllocator ().getNumAllocatedFragments () == 2 ); // The SESSION and the PAYLOAD BUFFER.
384
+ REQUIRE (ins.getAllocator ().getTotalAllocatedAmount () == (sizeof (RxSession) + 50 ));
385
+ REQUIRE (ins.getMessageSubs ().at (0 )->sessions [0b0100111 ] != nullptr );
386
+ ins.getAllocator ().deallocate (transfer.payload );
387
+ REQUIRE (ins.getAllocator ().getNumAllocatedFragments () == 1 ); // The payload buffer is gone.
388
+ REQUIRE (ins.getAllocator ().getTotalAllocatedAmount () == sizeof (RxSession));
389
+
390
+ // Next, feed the last frame of another transfer whose TID/TOG match the expected state of the reassembler,
391
+ // and the CRC matches the payload available in the last frame.
392
+ // This frame should be rejected because we didn't observe the first frame of this transfer.
393
+ // This failure mode may occur when the first frame is lost.
394
+ //
395
+ // Here's how we compute the reference value of the transfer CRC:
396
+ // >>> from pycyphal.transport.commons.crc import CRC16CCITT
397
+ // >>> CRC16CCITT.new(b'DUCK').value_as_bytes
398
+ // b'4\xa3'
399
+ subscription = nullptr ;
400
+ REQUIRE (0 == accept (100'001'001 , // The result should be zero because the transfer is rejected.
401
+ 0b001'00'0'11'0110011001100'0'0100111 , //
402
+ {' D' , ' U' , ' C' , ' K' , ' 4' , 0xA3 , 0b011'00001 })); // SOF=0, EOF=1, TOG=1, TID=1, CRC=0x4A34
403
+ REQUIRE (subscription != nullptr ); // Subscription exists.
404
+ REQUIRE (ins.getAllocator ().getNumAllocatedFragments () == 1 ); // The SESSION only.
405
+ REQUIRE (ins.getAllocator ().getTotalAllocatedAmount () == sizeof (RxSession));
406
+ REQUIRE (ins.getMessageSubs ().at (0 )->sessions [0b0100111 ] != nullptr );
407
+
408
+ // Now feed a similar transfer that is not malformed. It should be accepted.
409
+ // Here's how we compute the reference value of the transfer CRC:
410
+ // >>> from pycyphal.transport.commons.crc import CRC16CCITT
411
+ // >>> CRC16CCITT.new(b'\x01\x02\x03\x04\x05\x06\x07DUCK').value_as_bytes
412
+ // b'\xd3\x14'
413
+ subscription = nullptr ;
414
+ REQUIRE (0 == accept (100'002'001 , //
415
+ 0b001'00'0'11'0110011001100'0'0100111 ,
416
+ {1 , 2 , 3 , 4 , 5 , 6 , 7 , 0b101'00010 }));
417
+ REQUIRE (subscription != nullptr ); // Subscription exists.
418
+ REQUIRE (1 == accept (100'002'002 , // Accepted!
419
+ 0b001'00'0'11'0110011001100'0'0100111 ,
420
+ {' D' , ' U' , ' C' , ' K' , 0xD3 , 0x14 , 0b010'00010 }));
421
+ REQUIRE (subscription != nullptr ); // Subscription exists.
422
+ REQUIRE (subscription->port_id == 0b0110011001100 );
423
+ REQUIRE (transfer.timestamp_usec == 100'002'001 );
424
+ REQUIRE (transfer.metadata .priority == CanardPriorityImmediate);
425
+ REQUIRE (transfer.metadata .transfer_kind == CanardTransferKindMessage);
426
+ REQUIRE (transfer.metadata .port_id == 0b0110011001100 );
427
+ REQUIRE (transfer.metadata .remote_node_id == 0b0100111 );
428
+ REQUIRE (transfer.metadata .transfer_id == 2 );
429
+ REQUIRE (transfer.payload_size == 11 );
430
+ REQUIRE (0 == std::memcmp (transfer.payload ,
431
+ " \x01\x02\x03\x04\x05\x06\x07 "
432
+ " DUCK" ,
433
+ 11 ));
434
+ REQUIRE (ins.getAllocator ().getNumAllocatedFragments () == 2 ); // The SESSION and the PAYLOAD BUFFER.
435
+ REQUIRE (ins.getAllocator ().getTotalAllocatedAmount () == (sizeof (RxSession) + 50 ));
436
+ REQUIRE (ins.getMessageSubs ().at (0 )->sessions [0b0100111 ] != nullptr );
437
+ ins.getAllocator ().deallocate (transfer.payload );
438
+ REQUIRE (ins.getAllocator ().getNumAllocatedFragments () == 1 ); // The payload buffer is gone.
439
+ REQUIRE (ins.getAllocator ().getTotalAllocatedAmount () == sizeof (RxSession));
440
+ }
0 commit comments