@@ -10,14 +10,10 @@ void tearDown(void) {}
1010
1111static canard_filter_t make_filter (const canard_kind_t kind , const uint16_t port_id , const byte_t node_id )
1212{
13- canard_t self ;
14- canard_subscription_t sub ;
13+ canard_t self ;
1514 memset (& self , 0 , sizeof (self ));
16- memset (& sub , 0 , sizeof (sub ));
1715 self .node_id = node_id ;
18- sub .kind = kind ;
19- sub .port_id = port_id ;
20- return rx_filter_for_subscription (& self , & sub );
16+ return rx_filter_for_subscription (& self , kind , port_id );
2117}
2218
2319static bool filter_accepts (const canard_filter_t filter , const uint32_t can_id )
@@ -933,6 +929,237 @@ static void test_rx_filter_configure_coalescence_overflow(void)
933929 canard_destroy (& self );
934930}
935931
932+ // =====================================================================================================================
933+ // rx_filter_match()
934+
935+ static void test_rx_filter_match_empty (void )
936+ {
937+ TEST_ASSERT_FALSE (rx_filter_match (0 , NULL , 0x12345678U ));
938+ TEST_ASSERT_FALSE (rx_filter_match (0 , NULL , 0U ));
939+ }
940+
941+ static void test_rx_filter_match_single_hit (void )
942+ {
943+ const canard_filter_t f = make_filter (canard_kind_message_13b , 7509U , 42 );
944+ TEST_ASSERT_TRUE (rx_filter_match (1 , & f , f .extended_can_id ));
945+ // Also matches with different source node-ID (bits 6:0 are not masked).
946+ TEST_ASSERT_TRUE (rx_filter_match (1 , & f , f .extended_can_id | 1U ));
947+ TEST_ASSERT_TRUE (rx_filter_match (1 , & f , f .extended_can_id | 0x7FU ));
948+ }
949+
950+ static void test_rx_filter_match_single_miss (void )
951+ {
952+ const canard_filter_t f = make_filter (canard_kind_message_13b , 7509U , 42 );
953+ // A completely different subject-ID should not match.
954+ const canard_filter_t other = make_filter (canard_kind_message_13b , 100U , 42 );
955+ TEST_ASSERT_FALSE (rx_filter_match (1 , & f , other .extended_can_id ));
956+ // v0 message with same numeric ID should also not match (different mask/bits).
957+ const canard_filter_t v0 = make_filter (canard_kind_v0_message , 341U , 42 );
958+ TEST_ASSERT_FALSE (rx_filter_match (1 , & f , v0 .extended_can_id ));
959+ }
960+
961+ static void test_rx_filter_match_multiple (void )
962+ {
963+ const canard_filter_t arr [] = {
964+ make_filter (canard_kind_message_16b , 100U , 42 ),
965+ make_filter (canard_kind_message_13b , 200U , 42 ),
966+ make_filter (canard_kind_v0_message , 300U , 42 ),
967+ };
968+ // Each filter's own CAN ID should be matched.
969+ TEST_ASSERT_TRUE (rx_filter_match (3 , arr , arr [0 ].extended_can_id ));
970+ TEST_ASSERT_TRUE (rx_filter_match (3 , arr , arr [1 ].extended_can_id ));
971+ TEST_ASSERT_TRUE (rx_filter_match (3 , arr , arr [2 ].extended_can_id ));
972+ // Unrelated CAN ID should not match any.
973+ const canard_filter_t unrelated = make_filter (canard_kind_message_13b , 999U , 42 );
974+ TEST_ASSERT_FALSE (rx_filter_match (3 , arr , unrelated .extended_can_id ));
975+ }
976+
977+ // =====================================================================================================================
978+ // rx_filter_configure() forced Heartbeat/NodeStatus filters
979+
980+ #define HEARTBEAT_SUBJECT_ID 7509U
981+ #define NODESTATUS_DTYPE_ID 341U
982+
983+ static size_t g_cap_count ;
984+ static canard_filter_t g_cap_filters [32 ];
985+
986+ static bool capturing_filter_cb (canard_t * const self , const size_t filter_count , const canard_filter_t * const filters )
987+ {
988+ (void )self ;
989+ g_cap_count = filter_count ;
990+ for (size_t i = 0 ; i < filter_count && i < 32U ; i ++ ) {
991+ g_cap_filters [i ] = filters [i ];
992+ }
993+ return true;
994+ }
995+
996+ static const canard_vtable_t capturing_vtable = { .now = test_now_cb , .tx = test_tx_cb , .filter = capturing_filter_cb };
997+
998+ // Checks whether any of the captured filters accepts a given CAN ID.
999+ static bool captured_accepts (const uint32_t can_id )
1000+ {
1001+ for (size_t i = 0 ; i < g_cap_count ; i ++ ) {
1002+ if (filter_accepts (g_cap_filters [i ], can_id )) {
1003+ return true;
1004+ }
1005+ }
1006+ return false;
1007+ }
1008+
1009+ // Build a representative CAN ID for the forced message from an arbitrary source node.
1010+ static uint32_t heartbeat_can_id (const byte_t source )
1011+ {
1012+ return (HEARTBEAT_SUBJECT_ID << 8U ) | (source & CANARD_NODE_ID_MAX );
1013+ }
1014+ static uint32_t nodestatus_can_id (const byte_t source )
1015+ {
1016+ return (NODESTATUS_DTYPE_ID << 8U ) | (source & CANARD_NODE_ID_MAX );
1017+ }
1018+
1019+ static canard_t make_instance (const size_t filter_count )
1020+ {
1021+ const canard_mem_t real_mem = { .vtable = & real_mem_vtable , .context = NULL };
1022+ const canard_mem_set_t mem = { .tx_transfer = real_mem ,
1023+ .tx_frame = real_mem ,
1024+ .rx_session = real_mem ,
1025+ .rx_payload = real_mem ,
1026+ .rx_filters = real_mem };
1027+ canard_t self ;
1028+ memset (& self , 0 , sizeof (self ));
1029+ (void )canard_new (& self , & capturing_vtable , mem , 16U , 1234U , filter_count );
1030+ g_cap_count = 0 ;
1031+ memset (g_cap_filters , 0 , sizeof (g_cap_filters ));
1032+ return self ;
1033+ }
1034+
1035+ static void test_rx_filter_configure_forced_no_subs (void )
1036+ {
1037+ canard_t self = make_instance (4 );
1038+ TEST_ASSERT_TRUE (rx_filter_configure (& self ));
1039+ TEST_ASSERT_EQUAL_size_t (2U , g_cap_count ); // Heartbeat + NodeStatus
1040+ TEST_ASSERT_TRUE (captured_accepts (heartbeat_can_id (0 )));
1041+ TEST_ASSERT_TRUE (captured_accepts (heartbeat_can_id (42 )));
1042+ TEST_ASSERT_TRUE (captured_accepts (heartbeat_can_id (127 )));
1043+ TEST_ASSERT_TRUE (captured_accepts (nodestatus_can_id (0 )));
1044+ TEST_ASSERT_TRUE (captured_accepts (nodestatus_can_id (42 )));
1045+ TEST_ASSERT_TRUE (captured_accepts (nodestatus_can_id (127 )));
1046+ canard_destroy (& self );
1047+ }
1048+
1049+ static void test_rx_filter_configure_forced_heartbeat_subscribed (void )
1050+ {
1051+ canard_t self = make_instance (4 );
1052+ canard_subscription_t sub ;
1053+ TEST_ASSERT_TRUE (canard_subscribe_13b (& self , & sub , HEARTBEAT_SUBJECT_ID , 64U , 1000000 , & dummy_sub_vtable ));
1054+ TEST_ASSERT_TRUE (rx_filter_configure (& self ));
1055+ // 1 subscription filter + 1 forced NodeStatus = 2
1056+ TEST_ASSERT_EQUAL_size_t (2U , g_cap_count );
1057+ TEST_ASSERT_TRUE (captured_accepts (heartbeat_can_id (1 )));
1058+ TEST_ASSERT_TRUE (captured_accepts (nodestatus_can_id (1 )));
1059+ canard_unsubscribe (& self , & sub );
1060+ canard_destroy (& self );
1061+ }
1062+
1063+ static void test_rx_filter_configure_forced_nodestatus_subscribed (void )
1064+ {
1065+ canard_t self = make_instance (4 );
1066+ canard_subscription_t sub ;
1067+ TEST_ASSERT_TRUE (canard_v0_subscribe (& self , & sub , NODESTATUS_DTYPE_ID , 0 , 64U , 1000000 , & dummy_sub_vtable ));
1068+ TEST_ASSERT_TRUE (rx_filter_configure (& self ));
1069+ // 1 subscription filter + 1 forced Heartbeat = 2
1070+ TEST_ASSERT_EQUAL_size_t (2U , g_cap_count );
1071+ TEST_ASSERT_TRUE (captured_accepts (heartbeat_can_id (1 )));
1072+ TEST_ASSERT_TRUE (captured_accepts (nodestatus_can_id (1 )));
1073+ canard_unsubscribe (& self , & sub );
1074+ canard_destroy (& self );
1075+ }
1076+
1077+ static void test_rx_filter_configure_forced_both_subscribed (void )
1078+ {
1079+ canard_t self = make_instance (4 );
1080+ canard_subscription_t sub_hb ;
1081+ canard_subscription_t sub_ns ;
1082+ TEST_ASSERT_TRUE (canard_subscribe_13b (& self , & sub_hb , HEARTBEAT_SUBJECT_ID , 64U , 1000000 , & dummy_sub_vtable ));
1083+ TEST_ASSERT_TRUE (canard_v0_subscribe (& self , & sub_ns , NODESTATUS_DTYPE_ID , 0 , 64U , 1000000 , & dummy_sub_vtable ));
1084+ TEST_ASSERT_TRUE (rx_filter_configure (& self ));
1085+ // Both already covered by subscriptions, no extras.
1086+ TEST_ASSERT_EQUAL_size_t (2U , g_cap_count );
1087+ TEST_ASSERT_TRUE (captured_accepts (heartbeat_can_id (1 )));
1088+ TEST_ASSERT_TRUE (captured_accepts (nodestatus_can_id (1 )));
1089+ canard_unsubscribe (& self , & sub_hb );
1090+ canard_unsubscribe (& self , & sub_ns );
1091+ canard_destroy (& self );
1092+ }
1093+
1094+ static void test_rx_filter_configure_forced_capacity_1 (void )
1095+ {
1096+ canard_t self = make_instance (1 );
1097+ TEST_ASSERT_TRUE (rx_filter_configure (& self ));
1098+ // Heartbeat fills the slot, NodeStatus is coalesced into it.
1099+ TEST_ASSERT_EQUAL_size_t (1U , g_cap_count );
1100+ // The coalesced filter must still accept both.
1101+ TEST_ASSERT_TRUE (captured_accepts (heartbeat_can_id (1 )));
1102+ TEST_ASSERT_TRUE (captured_accepts (nodestatus_can_id (1 )));
1103+ canard_destroy (& self );
1104+ }
1105+
1106+ static void test_rx_filter_configure_forced_capacity_2_no_subs (void )
1107+ {
1108+ canard_t self = make_instance (2 );
1109+ TEST_ASSERT_TRUE (rx_filter_configure (& self ));
1110+ TEST_ASSERT_EQUAL_size_t (2U , g_cap_count );
1111+ // Each forced filter gets its own slot.
1112+ TEST_ASSERT_TRUE (captured_accepts (heartbeat_can_id (1 )));
1113+ TEST_ASSERT_TRUE (captured_accepts (nodestatus_can_id (1 )));
1114+ canard_destroy (& self );
1115+ }
1116+
1117+ static void test_rx_filter_configure_forced_with_unrelated_subs (void )
1118+ {
1119+ canard_t self = make_instance (10 );
1120+ canard_subscription_t sub1 , sub2 , sub3 ;
1121+ TEST_ASSERT_TRUE (canard_subscribe_16b (& self , & sub1 , 100U , 64U , 1000000 , & dummy_sub_vtable ));
1122+ TEST_ASSERT_TRUE (canard_subscribe_16b (& self , & sub2 , 200U , 64U , 1000000 , & dummy_sub_vtable ));
1123+ TEST_ASSERT_TRUE (canard_subscribe_16b (& self , & sub3 , 300U , 64U , 1000000 , & dummy_sub_vtable ));
1124+ TEST_ASSERT_TRUE (rx_filter_configure (& self ));
1125+ // 3 subs + 2 forced = 5
1126+ TEST_ASSERT_EQUAL_size_t (5U , g_cap_count );
1127+ TEST_ASSERT_TRUE (captured_accepts (heartbeat_can_id (1 )));
1128+ TEST_ASSERT_TRUE (captured_accepts (nodestatus_can_id (1 )));
1129+ // Subscriptions still work.
1130+ const canard_filter_t f1 = make_filter (canard_kind_message_16b , 100U , 0 );
1131+ const canard_filter_t f2 = make_filter (canard_kind_message_16b , 200U , 0 );
1132+ const canard_filter_t f3 = make_filter (canard_kind_message_16b , 300U , 0 );
1133+ TEST_ASSERT_TRUE (captured_accepts (f1 .extended_can_id ));
1134+ TEST_ASSERT_TRUE (captured_accepts (f2 .extended_can_id ));
1135+ TEST_ASSERT_TRUE (captured_accepts (f3 .extended_can_id ));
1136+ canard_unsubscribe (& self , & sub1 );
1137+ canard_unsubscribe (& self , & sub2 );
1138+ canard_unsubscribe (& self , & sub3 );
1139+ canard_destroy (& self );
1140+ }
1141+
1142+ static void test_rx_filter_configure_forced_overflow (void )
1143+ {
1144+ // capacity=1 with 2 unrelated subs: subs fill+coalesce, then forced filters also coalesce in.
1145+ canard_t self = make_instance (1 );
1146+ canard_subscription_t sub1 , sub2 ;
1147+ TEST_ASSERT_TRUE (canard_subscribe_16b (& self , & sub1 , 100U , 64U , 1000000 , & dummy_sub_vtable ));
1148+ TEST_ASSERT_TRUE (canard_subscribe_16b (& self , & sub2 , 200U , 64U , 1000000 , & dummy_sub_vtable ));
1149+ TEST_ASSERT_TRUE (rx_filter_configure (& self ));
1150+ TEST_ASSERT_EQUAL_size_t (1U , g_cap_count );
1151+ // After heavy coalescence the single filter should still accept all four CAN IDs.
1152+ const canard_filter_t f1 = make_filter (canard_kind_message_16b , 100U , 0 );
1153+ const canard_filter_t f2 = make_filter (canard_kind_message_16b , 200U , 0 );
1154+ TEST_ASSERT_TRUE (captured_accepts (f1 .extended_can_id ));
1155+ TEST_ASSERT_TRUE (captured_accepts (f2 .extended_can_id ));
1156+ TEST_ASSERT_TRUE (captured_accepts (heartbeat_can_id (1 )));
1157+ TEST_ASSERT_TRUE (captured_accepts (nodestatus_can_id (1 )));
1158+ canard_unsubscribe (& self , & sub1 );
1159+ canard_unsubscribe (& self , & sub2 );
1160+ canard_destroy (& self );
1161+ }
1162+
9361163// =====================================================================================================================
9371164
9381165int main (void )
@@ -991,5 +1218,19 @@ int main(void)
9911218 RUN_TEST (test_rx_filter_configure_oom );
9921219 RUN_TEST (test_rx_filter_configure_coalescence_overflow );
9931220
1221+ RUN_TEST (test_rx_filter_match_empty );
1222+ RUN_TEST (test_rx_filter_match_single_hit );
1223+ RUN_TEST (test_rx_filter_match_single_miss );
1224+ RUN_TEST (test_rx_filter_match_multiple );
1225+
1226+ RUN_TEST (test_rx_filter_configure_forced_no_subs );
1227+ RUN_TEST (test_rx_filter_configure_forced_heartbeat_subscribed );
1228+ RUN_TEST (test_rx_filter_configure_forced_nodestatus_subscribed );
1229+ RUN_TEST (test_rx_filter_configure_forced_both_subscribed );
1230+ RUN_TEST (test_rx_filter_configure_forced_capacity_1 );
1231+ RUN_TEST (test_rx_filter_configure_forced_capacity_2_no_subs );
1232+ RUN_TEST (test_rx_filter_configure_forced_with_unrelated_subs );
1233+ RUN_TEST (test_rx_filter_configure_forced_overflow );
1234+
9941235 return UNITY_END ();
9951236}
0 commit comments