Skip to content

Commit 423ae3e

Browse files
can id layout tests
1 parent 18e481c commit 423ae3e

File tree

1 file changed

+307
-0
lines changed

1 file changed

+307
-0
lines changed

tests/src/test_intrusive_tx.c

Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,305 @@ static void test_canard_0v1_service_capacity(void)
439439
TEST_ASSERT_EQUAL_size_t(0U, alloc.allocated_fragments);
440440
}
441441

442+
// =============================================
443+
// CAN ID specification compliance tests.
444+
// These tests verify CAN ID composition against hardcoded literals independently derived from the specifications,
445+
// ensuring that a systematic bit-field error cannot hide behind recomputation using the same expressions.
446+
// =============================================
447+
448+
// Cyphal v1.0 message broadcast CAN ID compliance.
449+
// Spec layout: [28:26]=prio [25]=0 [24]=0 [23]=0 [22:21]=11 [20:8]=subject_id [7:0]=0 (template, source filled later)
450+
static void test_1v0_publish_can_id_compliance(void)
451+
{
452+
canard_t self;
453+
test_context_t ctx;
454+
instrumented_allocator_t alloc;
455+
const canard_bytes_chain_t payload = { .bytes = { .size = 0U, .data = NULL }, .next = NULL };
456+
457+
// Case A: prio=exceptional(0), subject_id=0
458+
init_canard(&self, &ctx, &alloc, 8U);
459+
TEST_ASSERT_TRUE(
460+
canard_1v0_publish(&self, 1000, 1U, canard_prio_exceptional, 0U, 0U, payload, CANARD_USER_CONTEXT_NULL));
461+
{
462+
const canard_txfer_t* const tr = LIST_HEAD(self.tx.agewise, canard_txfer_t, list_agewise);
463+
TEST_ASSERT_NOT_NULL(tr);
464+
const uint32_t cid = can_id_from_transfer(tr);
465+
TEST_ASSERT_EQUAL_HEX32(0x00600000UL, cid);
466+
TEST_ASSERT_EQUAL_UINT32(0U, (cid >> 26U) & 7U); // priority
467+
TEST_ASSERT_EQUAL_UINT32(0U, (cid >> 25U) & 1U); // service=0
468+
TEST_ASSERT_EQUAL_UINT32(0U, (cid >> 24U) & 1U); // anonymous=0
469+
TEST_ASSERT_EQUAL_UINT32(0U, (cid >> 23U) & 1U); // reserved
470+
TEST_ASSERT_EQUAL_UINT32(3U, (cid >> 21U) & 3U); // reserved=11
471+
TEST_ASSERT_EQUAL_UINT32(0U, (cid >> 8U) & 0x1FFFU); // subject_id
472+
TEST_ASSERT_EQUAL_UINT32(0U, cid & 0xFFU); // bits[7:0]=0
473+
}
474+
free_all_transfers(&self);
475+
476+
// Case B: prio=optional(7), subject_id=8191
477+
init_canard(&self, &ctx, &alloc, 8U);
478+
TEST_ASSERT_TRUE(
479+
canard_1v0_publish(&self, 1000, 1U, canard_prio_optional, 8191U, 0U, payload, CANARD_USER_CONTEXT_NULL));
480+
{
481+
const canard_txfer_t* const tr = LIST_HEAD(self.tx.agewise, canard_txfer_t, list_agewise);
482+
TEST_ASSERT_NOT_NULL(tr);
483+
const uint32_t cid = can_id_from_transfer(tr);
484+
TEST_ASSERT_EQUAL_HEX32(0x1C7FFF00UL, cid);
485+
TEST_ASSERT_EQUAL_UINT32(7U, (cid >> 26U) & 7U);
486+
TEST_ASSERT_EQUAL_UINT32(8191U, (cid >> 8U) & 0x1FFFU);
487+
}
488+
free_all_transfers(&self);
489+
490+
// Case C: prio=high(3), subject_id=42
491+
init_canard(&self, &ctx, &alloc, 8U);
492+
TEST_ASSERT_TRUE(canard_1v0_publish(&self, 1000, 1U, canard_prio_high, 42U, 0U, payload, CANARD_USER_CONTEXT_NULL));
493+
{
494+
const canard_txfer_t* const tr = LIST_HEAD(self.tx.agewise, canard_txfer_t, list_agewise);
495+
TEST_ASSERT_NOT_NULL(tr);
496+
const uint32_t cid = can_id_from_transfer(tr);
497+
TEST_ASSERT_EQUAL_HEX32(0x0C602A00UL, cid);
498+
TEST_ASSERT_EQUAL_UINT32(3U, (cid >> 26U) & 7U);
499+
TEST_ASSERT_EQUAL_UINT32(42U, (cid >> 8U) & 0x1FFFU);
500+
}
501+
free_all_transfers(&self);
502+
}
503+
504+
// Cyphal v1.0 service request CAN ID compliance.
505+
// Spec layout: [28:26]=prio [25]=1 [24]=1 [23]=0 [22:14]=service_id [13:7]=dest [6:0]=0 (template)
506+
static void test_1v0_request_can_id_compliance(void)
507+
{
508+
canard_t self;
509+
test_context_t ctx;
510+
instrumented_allocator_t alloc;
511+
const canard_bytes_chain_t payload = { .bytes = { .size = 0U, .data = NULL }, .next = NULL };
512+
513+
// Case A: prio=exceptional(0), service_id=0, dest=1
514+
init_canard(&self, &ctx, &alloc, 8U);
515+
self.node_id = 10U;
516+
TEST_ASSERT_TRUE(
517+
canard_1v0_request(&self, 1000, canard_prio_exceptional, 0U, 1U, 0U, payload, CANARD_USER_CONTEXT_NULL));
518+
{
519+
const canard_txfer_t* const tr = LIST_HEAD(self.tx.agewise, canard_txfer_t, list_agewise);
520+
TEST_ASSERT_NOT_NULL(tr);
521+
const uint32_t cid = can_id_from_transfer(tr);
522+
TEST_ASSERT_EQUAL_HEX32(0x03000080UL, cid);
523+
TEST_ASSERT_EQUAL_UINT32(0U, (cid >> 26U) & 7U); // priority
524+
TEST_ASSERT_EQUAL_UINT32(1U, (cid >> 25U) & 1U); // service=1
525+
TEST_ASSERT_EQUAL_UINT32(1U, (cid >> 24U) & 1U); // request=1
526+
TEST_ASSERT_EQUAL_UINT32(0U, (cid >> 23U) & 1U); // reserved
527+
TEST_ASSERT_EQUAL_UINT32(0U, (cid >> 14U) & 0x1FFU); // service_id
528+
TEST_ASSERT_EQUAL_UINT32(1U, (cid >> 7U) & 0x7FU); // dest
529+
}
530+
free_all_transfers(&self);
531+
532+
// Case B: prio=optional(7), service_id=511, dest=127
533+
init_canard(&self, &ctx, &alloc, 8U);
534+
self.node_id = 10U;
535+
TEST_ASSERT_TRUE(
536+
canard_1v0_request(&self, 1000, canard_prio_optional, 511U, 127U, 0U, payload, CANARD_USER_CONTEXT_NULL));
537+
{
538+
const canard_txfer_t* const tr = LIST_HEAD(self.tx.agewise, canard_txfer_t, list_agewise);
539+
TEST_ASSERT_NOT_NULL(tr);
540+
const uint32_t cid = can_id_from_transfer(tr);
541+
TEST_ASSERT_EQUAL_HEX32(0x1F7FFF80UL, cid);
542+
TEST_ASSERT_EQUAL_UINT32(7U, (cid >> 26U) & 7U);
543+
TEST_ASSERT_EQUAL_UINT32(511U, (cid >> 14U) & 0x1FFU);
544+
TEST_ASSERT_EQUAL_UINT32(127U, (cid >> 7U) & 0x7FU);
545+
}
546+
free_all_transfers(&self);
547+
}
548+
549+
// Cyphal v1.0 service response CAN ID compliance.
550+
// Same as request but bit[24]=0 (response, not request).
551+
static void test_1v0_respond_can_id_compliance(void)
552+
{
553+
canard_t self;
554+
test_context_t ctx;
555+
instrumented_allocator_t alloc;
556+
const canard_bytes_chain_t payload = { .bytes = { .size = 0U, .data = NULL }, .next = NULL };
557+
558+
// Case A: prio=fast(2), service_id=430, dest=24
559+
init_canard(&self, &ctx, &alloc, 8U);
560+
self.node_id = 11U;
561+
TEST_ASSERT_TRUE(
562+
canard_1v0_respond(&self, 1000, canard_prio_fast, 430U, 24U, 0U, payload, CANARD_USER_CONTEXT_NULL));
563+
{
564+
const canard_txfer_t* const tr = LIST_HEAD(self.tx.agewise, canard_txfer_t, list_agewise);
565+
TEST_ASSERT_NOT_NULL(tr);
566+
const uint32_t cid = can_id_from_transfer(tr);
567+
TEST_ASSERT_EQUAL_HEX32(0x0A6B8C00UL, cid);
568+
TEST_ASSERT_EQUAL_UINT32(2U, (cid >> 26U) & 7U); // priority
569+
TEST_ASSERT_EQUAL_UINT32(1U, (cid >> 25U) & 1U); // service=1
570+
TEST_ASSERT_EQUAL_UINT32(0U, (cid >> 24U) & 1U); // request=0 (response)
571+
TEST_ASSERT_EQUAL_UINT32(430U, (cid >> 14U) & 0x1FFU); // service_id
572+
TEST_ASSERT_EQUAL_UINT32(24U, (cid >> 7U) & 0x7FU); // dest
573+
}
574+
free_all_transfers(&self);
575+
576+
// Case B: prio=nominal(4), service_id=1, dest=1
577+
init_canard(&self, &ctx, &alloc, 8U);
578+
self.node_id = 11U;
579+
TEST_ASSERT_TRUE(
580+
canard_1v0_respond(&self, 1000, canard_prio_nominal, 1U, 1U, 0U, payload, CANARD_USER_CONTEXT_NULL));
581+
{
582+
const canard_txfer_t* const tr = LIST_HEAD(self.tx.agewise, canard_txfer_t, list_agewise);
583+
TEST_ASSERT_NOT_NULL(tr);
584+
const uint32_t cid = can_id_from_transfer(tr);
585+
TEST_ASSERT_EQUAL_HEX32(0x12004080UL, cid);
586+
TEST_ASSERT_EQUAL_UINT32(4U, (cid >> 26U) & 7U);
587+
TEST_ASSERT_EQUAL_UINT32(1U, (cid >> 14U) & 0x1FFU);
588+
TEST_ASSERT_EQUAL_UINT32(1U, (cid >> 7U) & 0x7FU);
589+
}
590+
free_all_transfers(&self);
591+
}
592+
593+
// UAVCAN v0 message broadcast CAN ID compliance.
594+
// Spec layout: [28:24]=v0_prio [23:8]=dtid [7:0]=0 (template), where v0_prio=(cyphal_prio<<2)|3
595+
static void test_0v1_publish_can_id_compliance(void)
596+
{
597+
canard_t self;
598+
test_context_t ctx;
599+
instrumented_allocator_t alloc;
600+
const canard_bytes_chain_t payload = { .bytes = { .size = 0U, .data = NULL }, .next = NULL };
601+
602+
// Case A: prio=exceptional(0), dtid=0
603+
init_canard(&self, &ctx, &alloc, 8U);
604+
self.node_id = 1U;
605+
TEST_ASSERT_TRUE(
606+
canard_0v1_publish(&self, 1000, 1U, canard_prio_exceptional, 0U, 0xFFFFU, 0U, payload, CANARD_USER_CONTEXT_NULL));
607+
{
608+
const canard_txfer_t* const tr = LIST_HEAD(self.tx.agewise, canard_txfer_t, list_agewise);
609+
TEST_ASSERT_NOT_NULL(tr);
610+
const uint32_t cid = can_id_from_transfer(tr);
611+
TEST_ASSERT_EQUAL_HEX32(0x03000000UL, cid);
612+
TEST_ASSERT_EQUAL_UINT32(3U, (cid >> 24U) & 0x1FU); // v0_prio = (0<<2)|3 = 3
613+
TEST_ASSERT_EQUAL_UINT32(0U, (cid >> 8U) & 0xFFFFU); // dtid
614+
TEST_ASSERT_EQUAL_UINT32(0U, cid & 0xFFU); // bits[7:0]=0
615+
}
616+
free_all_transfers(&self);
617+
618+
// Case B: prio=optional(7), dtid=0xFFFF
619+
init_canard(&self, &ctx, &alloc, 8U);
620+
self.node_id = 1U;
621+
TEST_ASSERT_TRUE(canard_0v1_publish(
622+
&self, 1000, 1U, canard_prio_optional, 0xFFFFU, 0xFFFFU, 0U, payload, CANARD_USER_CONTEXT_NULL));
623+
{
624+
const canard_txfer_t* const tr = LIST_HEAD(self.tx.agewise, canard_txfer_t, list_agewise);
625+
TEST_ASSERT_NOT_NULL(tr);
626+
const uint32_t cid = can_id_from_transfer(tr);
627+
TEST_ASSERT_EQUAL_HEX32(0x1FFFFF00UL, cid);
628+
TEST_ASSERT_EQUAL_UINT32(0x1FU, (cid >> 24U) & 0x1FU); // v0_prio = (7<<2)|3 = 31
629+
TEST_ASSERT_EQUAL_UINT32(0xFFFFU, (cid >> 8U) & 0xFFFFU);
630+
}
631+
free_all_transfers(&self);
632+
633+
// Case C: prio=nominal(4), dtid=0x040A
634+
init_canard(&self, &ctx, &alloc, 8U);
635+
self.node_id = 1U;
636+
TEST_ASSERT_TRUE(canard_0v1_publish(
637+
&self, 1000, 1U, canard_prio_nominal, 0x040AU, 0xFFFFU, 0U, payload, CANARD_USER_CONTEXT_NULL));
638+
{
639+
const canard_txfer_t* const tr = LIST_HEAD(self.tx.agewise, canard_txfer_t, list_agewise);
640+
TEST_ASSERT_NOT_NULL(tr);
641+
const uint32_t cid = can_id_from_transfer(tr);
642+
TEST_ASSERT_EQUAL_HEX32(0x13040A00UL, cid);
643+
TEST_ASSERT_EQUAL_UINT32(0x13U, (cid >> 24U) & 0x1FU); // v0_prio = (4<<2)|3 = 19 = 0x13
644+
TEST_ASSERT_EQUAL_UINT32(0x040AU, (cid >> 8U) & 0xFFFFU);
645+
}
646+
free_all_transfers(&self);
647+
}
648+
649+
// UAVCAN v0 service request CAN ID compliance.
650+
// Spec layout: [28:24]=v0_prio [23:16]=dtid [15]=1(request) [14:8]=dest [7]=1 [6:0]=0 (template)
651+
static void test_0v1_request_can_id_compliance(void)
652+
{
653+
canard_t self;
654+
test_context_t ctx;
655+
instrumented_allocator_t alloc;
656+
const canard_bytes_chain_t payload = { .bytes = { .size = 0U, .data = NULL }, .next = NULL };
657+
658+
// Case A: prio=exceptional(0), dti=1, dest=1
659+
init_canard(&self, &ctx, &alloc, 8U);
660+
self.node_id = 10U;
661+
TEST_ASSERT_TRUE(
662+
canard_0v1_request(&self, 1000, canard_prio_exceptional, 1U, 0xFFFFU, 1U, 0U, payload, CANARD_USER_CONTEXT_NULL));
663+
{
664+
const canard_txfer_t* const tr = LIST_HEAD(self.tx.agewise, canard_txfer_t, list_agewise);
665+
TEST_ASSERT_NOT_NULL(tr);
666+
const uint32_t cid = can_id_from_transfer(tr);
667+
TEST_ASSERT_EQUAL_HEX32(0x03018180UL, cid);
668+
TEST_ASSERT_EQUAL_UINT32(3U, (cid >> 24U) & 0x1FU); // v0_prio = (0<<2)|3
669+
TEST_ASSERT_EQUAL_UINT32(1U, (cid >> 16U) & 0xFFU); // dtid
670+
TEST_ASSERT_EQUAL_UINT32(1U, (cid >> 15U) & 1U); // request=1
671+
TEST_ASSERT_EQUAL_UINT32(1U, (cid >> 8U) & 0x7FU); // dest
672+
TEST_ASSERT_EQUAL_UINT32(1U, (cid >> 7U) & 1U); // service=1
673+
}
674+
free_all_transfers(&self);
675+
676+
// Case B: prio=optional(7), dti=255, dest=127
677+
init_canard(&self, &ctx, &alloc, 8U);
678+
self.node_id = 10U;
679+
TEST_ASSERT_TRUE(canard_0v1_request(
680+
&self, 1000, canard_prio_optional, 255U, 0xFFFFU, 127U, 0U, payload, CANARD_USER_CONTEXT_NULL));
681+
{
682+
const canard_txfer_t* const tr = LIST_HEAD(self.tx.agewise, canard_txfer_t, list_agewise);
683+
TEST_ASSERT_NOT_NULL(tr);
684+
const uint32_t cid = can_id_from_transfer(tr);
685+
TEST_ASSERT_EQUAL_HEX32(0x1FFFFF80UL, cid);
686+
TEST_ASSERT_EQUAL_UINT32(0x1FU, (cid >> 24U) & 0x1FU);
687+
TEST_ASSERT_EQUAL_UINT32(255U, (cid >> 16U) & 0xFFU);
688+
TEST_ASSERT_EQUAL_UINT32(1U, (cid >> 15U) & 1U);
689+
TEST_ASSERT_EQUAL_UINT32(127U, (cid >> 8U) & 0x7FU);
690+
TEST_ASSERT_EQUAL_UINT32(1U, (cid >> 7U) & 1U);
691+
}
692+
free_all_transfers(&self);
693+
}
694+
695+
// UAVCAN v0 service response CAN ID compliance.
696+
// Same as request but bit[15]=0 (response, not request).
697+
static void test_0v1_respond_can_id_compliance(void)
698+
{
699+
canard_t self;
700+
test_context_t ctx;
701+
instrumented_allocator_t alloc;
702+
const canard_bytes_chain_t payload = { .bytes = { .size = 0U, .data = NULL }, .next = NULL };
703+
704+
// Case A: prio=nominal(4), dti=0x37, dest=24
705+
init_canard(&self, &ctx, &alloc, 8U);
706+
self.node_id = 11U;
707+
TEST_ASSERT_TRUE(
708+
canard_0v1_respond(&self, 1000, canard_prio_nominal, 0x37U, 0xFFFFU, 24U, 0U, payload, CANARD_USER_CONTEXT_NULL));
709+
{
710+
const canard_txfer_t* const tr = LIST_HEAD(self.tx.agewise, canard_txfer_t, list_agewise);
711+
TEST_ASSERT_NOT_NULL(tr);
712+
const uint32_t cid = can_id_from_transfer(tr);
713+
TEST_ASSERT_EQUAL_HEX32(0x13371880UL, cid);
714+
TEST_ASSERT_EQUAL_UINT32(0x13U, (cid >> 24U) & 0x1FU); // v0_prio = (4<<2)|3 = 19
715+
TEST_ASSERT_EQUAL_UINT32(0x37U, (cid >> 16U) & 0xFFU); // dtid
716+
TEST_ASSERT_EQUAL_UINT32(0U, (cid >> 15U) & 1U); // request=0 (response)
717+
TEST_ASSERT_EQUAL_UINT32(24U, (cid >> 8U) & 0x7FU); // dest
718+
TEST_ASSERT_EQUAL_UINT32(1U, (cid >> 7U) & 1U); // service=1
719+
}
720+
free_all_transfers(&self);
721+
722+
// Case B: prio=immediate(1), dti=200, dest=42
723+
init_canard(&self, &ctx, &alloc, 8U);
724+
self.node_id = 11U;
725+
TEST_ASSERT_TRUE(canard_0v1_respond(
726+
&self, 1000, canard_prio_immediate, 200U, 0xFFFFU, 42U, 0U, payload, CANARD_USER_CONTEXT_NULL));
727+
{
728+
const canard_txfer_t* const tr = LIST_HEAD(self.tx.agewise, canard_txfer_t, list_agewise);
729+
TEST_ASSERT_NOT_NULL(tr);
730+
const uint32_t cid = can_id_from_transfer(tr);
731+
TEST_ASSERT_EQUAL_HEX32(0x07C82A80UL, cid);
732+
TEST_ASSERT_EQUAL_UINT32(7U, (cid >> 24U) & 0x1FU); // v0_prio = (1<<2)|3 = 7
733+
TEST_ASSERT_EQUAL_UINT32(200U, (cid >> 16U) & 0xFFU);
734+
TEST_ASSERT_EQUAL_UINT32(0U, (cid >> 15U) & 1U);
735+
TEST_ASSERT_EQUAL_UINT32(42U, (cid >> 8U) & 0x7FU);
736+
TEST_ASSERT_EQUAL_UINT32(1U, (cid >> 7U) & 1U);
737+
}
738+
free_all_transfers(&self);
739+
}
740+
442741
void setUp(void) {}
443742

444743
void tearDown(void) {}
@@ -470,5 +769,13 @@ int main(void)
470769
RUN_TEST(test_canard_0v1_service_oom);
471770
RUN_TEST(test_canard_0v1_service_capacity);
472771

772+
// CAN ID specification compliance (hardcoded literals from specs).
773+
RUN_TEST(test_1v0_publish_can_id_compliance);
774+
RUN_TEST(test_1v0_request_can_id_compliance);
775+
RUN_TEST(test_1v0_respond_can_id_compliance);
776+
RUN_TEST(test_0v1_publish_can_id_compliance);
777+
RUN_TEST(test_0v1_request_can_id_compliance);
778+
RUN_TEST(test_0v1_respond_can_id_compliance);
779+
473780
return UNITY_END();
474781
}

0 commit comments

Comments
 (0)