/* * Copyright (c) 2019 Alexander Wachter * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include "random_data.h" #if !defined(CONFIG_TEST_USE_CAN_FD_MODE) || CONFIG_TEST_ISOTP_TX_DL == 8 #define DATA_SIZE_SF 7 #define DATA_SIZE_CF 7 #define DATA_SIZE_SF_EXT 6 #define DATA_SIZE_FF 6 #define TX_DL 8 #define DATA_SEND_LENGTH 272 #define SF_PCI_BYTE_1 ((SF_PCI_TYPE << PCI_TYPE_POS) | DATA_SIZE_SF) #define SF_PCI_BYTE_2_EXT ((SF_PCI_TYPE << PCI_TYPE_POS) | DATA_SIZE_SF_EXT) #define SF_PCI_BYTE_LEN_8 ((SF_PCI_TYPE << PCI_TYPE_POS) | (DATA_SIZE_SF + 1)) #else #define DATA_SIZE_SF (TX_DL - 2) #define DATA_SIZE_CF (TX_DL - 1) #define DATA_SIZE_SF_EXT (TX_DL - 3) #define DATA_SIZE_FF (TX_DL - 2) #define TX_DL CONFIG_TEST_ISOTP_TX_DL /* Send length must be larger than FF + (8 * CF). * But not so big that the remainder cannot fit into the buffers. */ #define DATA_SEND_LENGTH (100 + DATA_SIZE_FF + (8 * DATA_SIZE_CF)) #define SF_PCI_BYTE_1 (SF_PCI_TYPE << PCI_TYPE_POS) #define SF_PCI_BYTE_2 DATA_SIZE_SF #define SF_PCI_BYTE_2_EXT (SF_PCI_TYPE << PCI_TYPE_POS) #define SF_PCI_BYTE_3_EXT DATA_SIZE_SF_EXT #endif #define DATA_SIZE_FC 3 #define PCI_TYPE_POS 4 #define SF_PCI_TYPE 0 #define EXT_ADDR 5 #define FF_PCI_TYPE 1 #define FF_PCI_BYTE_1(dl) ((FF_PCI_TYPE << PCI_TYPE_POS) | ((dl) >> 8)) #define FF_PCI_BYTE_2(dl) ((dl) & 0xFF) #define FC_PCI_TYPE 3 #define FC_PCI_CTS 0 #define FC_PCI_WAIT 1 #define FC_PCI_OVFLW 2 #define FC_PCI_BYTE_1(FS) ((FC_PCI_TYPE << PCI_TYPE_POS) | (FS)) #define FC_PCI_BYTE_2(BS) (BS) #define FC_PCI_BYTE_3(ST_MIN) (ST_MIN) #define CF_PCI_TYPE 2 #define CF_PCI_BYTE_1 (CF_PCI_TYPE << PCI_TYPE_POS) #define STMIN_VAL_1 5 #define STMIN_VAL_2 50 #define STMIN_UPPER_TOLERANCE 5 #define BS_TIMEOUT_UPPER_MS 1100 #define BS_TIMEOUT_LOWER_MS 1000 /* * @addtogroup t_can * @{ * @defgroup t_can_isotp test_can_isotp * @brief TestPurpose: verify correctness of the iso tp implementation * @details * - Test Steps * -# * - Expected Results * -# * @} */ struct frame_desired { uint8_t data[CAN_MAX_DLEN]; uint8_t length; }; struct frame_desired des_frames[DIV_ROUND_UP((DATA_SEND_LENGTH - DATA_SIZE_FF), DATA_SIZE_CF)]; const struct isotp_fc_opts fc_opts = { .bs = 8, .stmin = 0 }; const struct isotp_fc_opts fc_opts_single = { .bs = 0, .stmin = 0 }; const struct isotp_msg_id rx_addr = { .std_id = 0x10, #ifdef CONFIG_TEST_USE_CAN_FD_MODE .dl = CONFIG_TEST_ISOTP_TX_DL, .flags = ISOTP_MSG_FDF | ISOTP_MSG_BRS, #endif }; const struct isotp_msg_id tx_addr = { .std_id = 0x11, #ifdef CONFIG_TEST_USE_CAN_FD_MODE .dl = CONFIG_TEST_ISOTP_TX_DL, .flags = ISOTP_MSG_FDF | ISOTP_MSG_BRS, #endif }; const struct isotp_msg_id rx_addr_ext = { .std_id = 0x10, .ext_addr = EXT_ADDR, #ifdef CONFIG_TEST_USE_CAN_FD_MODE .dl = CONFIG_TEST_ISOTP_TX_DL, .flags = ISOTP_MSG_EXT_ADDR | ISOTP_MSG_FDF | ISOTP_MSG_BRS, #else .flags = ISOTP_MSG_EXT_ADDR, #endif }; const struct isotp_msg_id tx_addr_ext = { .std_id = 0x11, .ext_addr = EXT_ADDR, #ifdef CONFIG_TEST_USE_CAN_FD_MODE .dl = CONFIG_TEST_ISOTP_TX_DL, .flags = ISOTP_MSG_EXT_ADDR | ISOTP_MSG_FDF | ISOTP_MSG_BRS, #else .flags = ISOTP_MSG_EXT_ADDR, #endif }; const struct isotp_msg_id rx_addr_fixed = { .ext_id = 0x18DA0201, #ifdef CONFIG_TEST_USE_CAN_FD_MODE .dl = CONFIG_TEST_ISOTP_TX_DL, .flags = ISOTP_MSG_FIXED_ADDR | ISOTP_MSG_IDE | ISOTP_MSG_FDF | ISOTP_MSG_BRS, #else .flags = ISOTP_MSG_FIXED_ADDR | ISOTP_MSG_IDE, #endif }; const struct isotp_msg_id tx_addr_fixed = { .ext_id = 0x18DA0102, #ifdef CONFIG_TEST_USE_CAN_FD_MODE .dl = CONFIG_TEST_ISOTP_TX_DL, .flags = ISOTP_MSG_FIXED_ADDR | ISOTP_MSG_IDE | ISOTP_MSG_FDF | ISOTP_MSG_BRS, #else .flags = ISOTP_MSG_FIXED_ADDR | ISOTP_MSG_IDE, #endif }; static const struct device *const can_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_canbus)); static struct isotp_recv_ctx recv_ctx; static struct isotp_send_ctx send_ctx; static uint8_t data_buf[128]; CAN_MSGQ_DEFINE(frame_msgq, 10); static struct k_sem send_compl_sem; void send_complete_cb(int error_nr, void *arg) { int expected_err_nr = POINTER_TO_INT(arg); zassert_equal(error_nr, expected_err_nr, "Unexpected error nr. expect: %d, got %d", expected_err_nr, error_nr); k_sem_give(&send_compl_sem); } static void print_hex(const uint8_t *ptr, size_t len) { while (len--) { printk("%02x ", *ptr++); } } static int check_data(const uint8_t *frame, const uint8_t *desired, size_t length) { int ret; ret = memcmp(frame, desired, length); if (ret) { printk("desired bytes:\n"); print_hex(desired, length); printk("\nreceived (%zu bytes):\n", length); print_hex(frame, length); printk("\n"); } return ret; } static void send_sf(void) { int ret; ret = isotp_send(&send_ctx, can_dev, random_data, DATA_SIZE_SF, &rx_addr, &tx_addr, send_complete_cb, INT_TO_POINTER(ISOTP_N_OK)); zassert_equal(ret, 0, "Send returned %d", ret); } static void get_sf(size_t data_size) { int ret; memset(data_buf, 0, sizeof(data_buf)); ret = isotp_recv(&recv_ctx, data_buf, sizeof(data_buf), K_MSEC(1000)); zassert_equal(ret, data_size, "recv returned %d", ret); ret = check_data(data_buf, random_data, data_size); zassert_equal(ret, 0, "Data differ"); } static void get_sf_ignore(void) { int ret; ret = isotp_recv(&recv_ctx, data_buf, sizeof(data_buf), K_MSEC(200)); zassert_equal(ret, ISOTP_RECV_TIMEOUT, "recv returned %d", ret); } static void send_test_data(const uint8_t *data, size_t len) { int ret; ret = isotp_send(&send_ctx, can_dev, data, len, &rx_addr, &tx_addr, send_complete_cb, INT_TO_POINTER(ISOTP_N_OK)); zassert_equal(ret, 0, "Send returned %d", ret); } static void receive_test_data(const uint8_t *data, size_t len, int32_t delay) { size_t remaining_len = len; int ret, recv_len; const uint8_t *data_ptr = data; do { memset(data_buf, 0, sizeof(data_buf)); recv_len = isotp_recv(&recv_ctx, data_buf, sizeof(data_buf), K_MSEC(1000)); zassert_true(recv_len >= 0, "recv error: %d", recv_len); zassert_true(remaining_len >= recv_len, "More data then expected"); ret = check_data(data_buf, data_ptr, recv_len); zassert_equal(ret, 0, "Data differ"); data_ptr += recv_len; remaining_len -= recv_len; if (delay) { k_msleep(delay); } } while (remaining_len); ret = isotp_recv(&recv_ctx, data_buf, sizeof(data_buf), K_MSEC(50)); zassert_equal(ret, ISOTP_RECV_TIMEOUT, "Expected timeout but got %d", ret); } static void send_frame_series(struct frame_desired *frames, size_t length, uint32_t id) { int i, ret; struct can_frame frame = { .flags = ((id > 0x7FF) ? CAN_FRAME_IDE : 0) | (IS_ENABLED(CONFIG_TEST_USE_CAN_FD_MODE) ? CAN_FRAME_FDF | CAN_FRAME_BRS : 0), .id = id }; struct frame_desired *desired = frames; for (i = 0; i < length; i++) { frame.dlc = can_bytes_to_dlc(desired->length); memcpy(frame.data, desired->data, desired->length); ret = can_send(can_dev, &frame, K_MSEC(500), NULL, NULL); zassert_equal(ret, 0, "Sending msg %d failed.", i); desired++; } } static void check_frame_series(struct frame_desired *frames, size_t length, struct k_msgq *msgq) { int i, ret; struct can_frame frame; struct frame_desired *desired = frames; for (i = 0; i < length; i++) { ret = k_msgq_get(msgq, &frame, K_MSEC(500)); zassert_equal(ret, 0, "Timeout waiting for msg nr %d. ret: %d", i, ret); zassert_equal(frame.dlc, can_bytes_to_dlc(desired->length), "DLC of frame nr %d differ. Desired: %d, Got: %d", i, can_bytes_to_dlc(desired->length), frame.dlc); ret = check_data(frame.data, desired->data, desired->length); zassert_equal(ret, 0, "Data differ"); desired++; } ret = k_msgq_get(msgq, &frame, K_MSEC(200)); zassert_equal(ret, -EAGAIN, "Expected timeout, but received %d", ret); } static int add_rx_msgq(uint32_t id, uint32_t mask) { int filter_id; struct can_filter filter = { .flags = (id > 0x7FF) ? CAN_FILTER_IDE : 0, .id = id, .mask = mask }; filter_id = can_add_rx_filter_msgq(can_dev, &frame_msgq, &filter); zassert_not_equal(filter_id, -ENOSPC, "Filter full"); zassert_true((filter_id >= 0), "Negative filter number [%d]", filter_id); return filter_id; } static void prepare_fc_frame(struct frame_desired *frame, uint8_t st, const struct isotp_fc_opts *opts, bool tx) { frame->data[0] = FC_PCI_BYTE_1(st); frame->data[1] = FC_PCI_BYTE_2(opts->bs); frame->data[2] = FC_PCI_BYTE_3(opts->stmin); if ((IS_ENABLED(CONFIG_ISOTP_ENABLE_TX_PADDING) && tx) || (IS_ENABLED(CONFIG_ISOTP_REQUIRE_RX_PADDING) && !tx)) { memset(&frame->data[DATA_SIZE_FC], 0xCC, 8 - DATA_SIZE_FC); frame->length = 8; } else { frame->length = DATA_SIZE_FC; } } static void prepare_sf_frame(struct frame_desired *frame, const uint8_t *data) { frame->data[0] = SF_PCI_BYTE_1; #ifdef SF_PCI_BYTE_2 frame->data[1] = SF_PCI_BYTE_2; memcpy(&frame->data[2], data, DATA_SIZE_SF); frame->length = DATA_SIZE_SF + 2; #else memcpy(&frame->data[1], data, DATA_SIZE_SF); frame->length = DATA_SIZE_SF + 1; #endif } static void prepare_sf_ext_frame(struct frame_desired *frame, const uint8_t *data) { frame->data[0] = rx_addr_ext.ext_addr; frame->data[1] = SF_PCI_BYTE_2_EXT; #ifdef SF_PCI_BYTE_3_EXT frame->data[2] = SF_PCI_BYTE_3_EXT; memcpy(&frame->data[3], data, DATA_SIZE_SF_EXT); frame->length = DATA_SIZE_SF_EXT + 3; #else memcpy(&frame->data[2], data, DATA_SIZE_SF_EXT); frame->length = DATA_SIZE_SF_EXT + 2; #endif } static void prepare_cf_frames(struct frame_desired *frames, size_t frames_cnt, const uint8_t *data, size_t data_len, bool tx) { int i; const uint8_t *data_ptr = data; size_t remaining_length = data_len; for (i = 0; i < frames_cnt && remaining_length; i++) { frames[i].data[0] = CF_PCI_BYTE_1 | ((i+1) & 0x0F); frames[i].length = TX_DL; memcpy(&frames[i].data[1], data_ptr, DATA_SIZE_CF); if (remaining_length < DATA_SIZE_CF) { if ((IS_ENABLED(CONFIG_ISOTP_ENABLE_TX_PADDING) && tx) || (IS_ENABLED(CONFIG_ISOTP_REQUIRE_RX_PADDING) && !tx)) { uint8_t padded_dlc = can_bytes_to_dlc(MAX(8, remaining_length + 1)); uint8_t padded_len = can_dlc_to_bytes(padded_dlc); memset(&frames[i].data[remaining_length + 1], 0xCC, padded_len - remaining_length - 1); frames[i].length = padded_len; } else { frames[i].length = remaining_length + 1; } remaining_length = 0; } remaining_length -= DATA_SIZE_CF; data_ptr += DATA_SIZE_CF; } } ZTEST(isotp_conformance, test_send_sf) { int filter_id; struct frame_desired des_frame; prepare_sf_frame(&des_frame, random_data); filter_id = add_rx_msgq(rx_addr.std_id, CAN_STD_ID_MASK); zassert_true((filter_id >= 0), "Negative filter number [%d]", filter_id); send_sf(); check_frame_series(&des_frame, 1, &frame_msgq); can_remove_rx_filter(can_dev, filter_id); } ZTEST(isotp_conformance, test_receive_sf) { int ret; struct frame_desired single_frame; prepare_sf_frame(&single_frame, random_data); ret = isotp_bind(&recv_ctx, can_dev, &rx_addr, &tx_addr, &fc_opts_single, K_NO_WAIT); zassert_equal(ret, ISOTP_N_OK, "Binding failed [%d]", ret); send_frame_series(&single_frame, 1, rx_addr.std_id); get_sf(DATA_SIZE_SF); /* Frame size too big should be ignored/dropped */ #ifdef SF_PCI_BYTE_2 single_frame.data[1]++; #else single_frame.data[0] = SF_PCI_BYTE_LEN_8; #endif send_frame_series(&single_frame, 1, rx_addr.std_id); get_sf_ignore(); #ifdef CONFIG_ISOTP_REQUIRE_RX_PADDING single_frame.data[0] = SF_PCI_BYTE_1; single_frame.length = 7; send_frame_series(&single_frame, 1, rx_addr.std_id); get_sf_ignore(); #endif isotp_unbind(&recv_ctx); } ZTEST(isotp_conformance, test_send_sf_ext) { int filter_id, ret; struct frame_desired des_frame; prepare_sf_ext_frame(&des_frame, random_data); filter_id = add_rx_msgq(rx_addr_ext.std_id, CAN_STD_ID_MASK); zassert_true((filter_id >= 0), "Negative filter number [%d]", filter_id); ret = isotp_send(&send_ctx, can_dev, random_data, DATA_SIZE_SF_EXT, &rx_addr_ext, &tx_addr_ext, send_complete_cb, INT_TO_POINTER(ISOTP_N_OK)); zassert_equal(ret, 0, "Send returned %d", ret); check_frame_series(&des_frame, 1, &frame_msgq); can_remove_rx_filter(can_dev, filter_id); } ZTEST(isotp_conformance, test_receive_sf_ext) { int ret; struct frame_desired single_frame; prepare_sf_ext_frame(&single_frame, random_data); ret = isotp_bind(&recv_ctx, can_dev, &rx_addr_ext, &tx_addr, &fc_opts_single, K_NO_WAIT); zassert_equal(ret, ISOTP_N_OK, "Binding failed [%d]", ret); send_frame_series(&single_frame, 1, rx_addr.std_id); get_sf(DATA_SIZE_SF_EXT); /* Frame size too big should be ignored/dropped */ #ifdef SF_PCI_BYTE_2 single_frame.data[2]++; #else single_frame.data[1] = SF_PCI_BYTE_1; #endif send_frame_series(&single_frame, 1, rx_addr.std_id); get_sf_ignore(); #ifdef CONFIG_ISOTP_REQUIRE_RX_PADDING single_frame.data[1] = SF_PCI_BYTE_2_EXT; single_frame.length = 7; send_frame_series(&single_frame, 1, rx_addr.std_id); get_sf_ignore(); #endif isotp_unbind(&recv_ctx); } ZTEST(isotp_conformance, test_send_sf_fixed) { int filter_id, ret; struct frame_desired des_frame; prepare_sf_frame(&des_frame, random_data); /* mask to allow any priority and source address (SA) */ filter_id = add_rx_msgq(rx_addr_fixed.ext_id, 0x03FFFF00); zassert_true((filter_id >= 0), "Negative filter number [%d]", filter_id); ret = isotp_send(&send_ctx, can_dev, random_data, DATA_SIZE_SF, &rx_addr_fixed, &tx_addr_fixed, send_complete_cb, INT_TO_POINTER(ISOTP_N_OK)); zassert_equal(ret, 0, "Send returned %d", ret); check_frame_series(&des_frame, 1, &frame_msgq); can_remove_rx_filter(can_dev, filter_id); } ZTEST(isotp_conformance, test_receive_sf_fixed) { int ret; struct frame_desired single_frame; prepare_sf_frame(&single_frame, random_data); ret = isotp_bind(&recv_ctx, can_dev, &rx_addr_fixed, &tx_addr_fixed, &fc_opts_single, K_NO_WAIT); zassert_equal(ret, ISOTP_N_OK, "Binding failed [%d]", ret); /* default source address */ send_frame_series(&single_frame, 1, rx_addr_fixed.ext_id); get_sf(DATA_SIZE_SF); /* different source address */ send_frame_series(&single_frame, 1, rx_addr_fixed.ext_id | 0xFF); get_sf(DATA_SIZE_SF); /* different priority */ send_frame_series(&single_frame, 1, rx_addr_fixed.ext_id | (7U << 26)); get_sf(DATA_SIZE_SF); /* different target address (should fail) */ send_frame_series(&single_frame, 1, rx_addr_fixed.ext_id | 0xFF00); get_sf_ignore(); isotp_unbind(&recv_ctx); } ZTEST(isotp_conformance, test_send_data) { struct frame_desired fc_frame, ff_frame; const uint8_t *data_ptr = random_data; size_t remaining_length = DATA_SEND_LENGTH; int filter_id; ff_frame.data[0] = FF_PCI_BYTE_1(DATA_SEND_LENGTH); ff_frame.data[1] = FF_PCI_BYTE_2(DATA_SEND_LENGTH); memcpy(&ff_frame.data[2], data_ptr, DATA_SIZE_FF); ff_frame.length = TX_DL; data_ptr += DATA_SIZE_FF; remaining_length -= DATA_SIZE_FF; prepare_fc_frame(&fc_frame, FC_PCI_CTS, &fc_opts_single, false); prepare_cf_frames(des_frames, ARRAY_SIZE(des_frames), data_ptr, remaining_length, true); filter_id = add_rx_msgq(rx_addr.std_id, CAN_STD_ID_MASK); zassert_true((filter_id >= 0), "Negative filter number [%d]", filter_id); send_test_data(random_data, DATA_SEND_LENGTH); check_frame_series(&ff_frame, 1, &frame_msgq); send_frame_series(&fc_frame, 1, tx_addr.std_id); check_frame_series(des_frames, ARRAY_SIZE(des_frames), &frame_msgq); can_remove_rx_filter(can_dev, filter_id); } ZTEST(isotp_conformance, test_send_data_blocks) { const uint8_t *data_ptr = random_data; size_t remaining_length = DATA_SEND_LENGTH; struct frame_desired *data_frame_ptr = des_frames; int filter_id, ret; struct can_frame dummy_frame; struct frame_desired fc_frame, ff_frame; ff_frame.data[0] = FF_PCI_BYTE_1(DATA_SEND_LENGTH); ff_frame.data[1] = FF_PCI_BYTE_2(DATA_SEND_LENGTH); memcpy(&ff_frame.data[2], data_ptr, DATA_SIZE_FF); ff_frame.length = DATA_SIZE_FF + 2; data_ptr += DATA_SIZE_FF; remaining_length -= DATA_SIZE_FF; prepare_fc_frame(&fc_frame, FC_PCI_CTS, &fc_opts, false); prepare_cf_frames(des_frames, ARRAY_SIZE(des_frames), data_ptr, remaining_length, true); remaining_length = DATA_SEND_LENGTH; filter_id = add_rx_msgq(rx_addr.std_id, CAN_STD_ID_MASK); zassert_true((filter_id >= 0), "Negative filter number [%d]", filter_id); send_test_data(random_data, DATA_SEND_LENGTH); check_frame_series(&ff_frame, 1, &frame_msgq); remaining_length -= DATA_SIZE_FF; send_frame_series(&fc_frame, 1, tx_addr.std_id); check_frame_series(data_frame_ptr, fc_opts.bs, &frame_msgq); data_frame_ptr += fc_opts.bs; remaining_length -= fc_opts.bs * DATA_SIZE_CF; ret = k_msgq_get(&frame_msgq, &dummy_frame, K_MSEC(50)); zassert_equal(ret, -EAGAIN, "Expected timeout but got %d", ret); fc_frame.data[1] = FC_PCI_BYTE_2(2); send_frame_series(&fc_frame, 1, tx_addr.std_id); /* dynamic bs */ check_frame_series(data_frame_ptr, 2, &frame_msgq); data_frame_ptr += 2; remaining_length -= 2 * DATA_SIZE_CF; ret = k_msgq_get(&frame_msgq, &dummy_frame, K_MSEC(50)); zassert_equal(ret, -EAGAIN, "Expected timeout but got %d", ret); /* get the rest */ fc_frame.data[1] = FC_PCI_BYTE_2(0); send_frame_series(&fc_frame, 1, tx_addr.std_id); check_frame_series(data_frame_ptr, DIV_ROUND_UP(remaining_length, DATA_SIZE_CF), &frame_msgq); ret = k_msgq_get(&frame_msgq, &dummy_frame, K_MSEC(50)); zassert_equal(ret, -EAGAIN, "Expected timeout but got %d", ret); can_remove_rx_filter(can_dev, filter_id); } ZTEST(isotp_conformance, test_receive_data) { const uint8_t *data_ptr = random_data; size_t remaining_length = DATA_SEND_LENGTH; int filter_id, ret; struct frame_desired fc_frame, ff_frame; ff_frame.data[0] = FF_PCI_BYTE_1(DATA_SEND_LENGTH); ff_frame.data[1] = FF_PCI_BYTE_2(DATA_SEND_LENGTH); memcpy(&ff_frame.data[2], data_ptr, DATA_SIZE_FF); ff_frame.length = TX_DL; data_ptr += DATA_SIZE_FF; remaining_length -= DATA_SIZE_FF; prepare_fc_frame(&fc_frame, FC_PCI_CTS, &fc_opts_single, true); prepare_cf_frames(des_frames, ARRAY_SIZE(des_frames), data_ptr, remaining_length, false); filter_id = add_rx_msgq(tx_addr.std_id, CAN_STD_ID_MASK); ret = isotp_bind(&recv_ctx, can_dev, &rx_addr, &tx_addr, &fc_opts_single, K_NO_WAIT); zassert_equal(ret, ISOTP_N_OK, "Binding failed [%d]", ret); send_frame_series(&ff_frame, 1, rx_addr.std_id); check_frame_series(&fc_frame, 1, &frame_msgq); send_frame_series(des_frames, ARRAY_SIZE(des_frames), rx_addr.std_id); receive_test_data(random_data, DATA_SEND_LENGTH, 0); can_remove_rx_filter(can_dev, filter_id); isotp_unbind(&recv_ctx); } ZTEST(isotp_conformance, test_receive_data_blocks) { const uint8_t *data_ptr = random_data; size_t remaining_length = DATA_SEND_LENGTH; struct frame_desired *data_frame_ptr = des_frames; int filter_id, ret; size_t remaining_frames; struct frame_desired fc_frame, ff_frame; struct can_frame dummy_frame; ff_frame.data[0] = FF_PCI_BYTE_1(DATA_SEND_LENGTH); ff_frame.data[1] = FF_PCI_BYTE_2(DATA_SEND_LENGTH); memcpy(&ff_frame.data[2], data_ptr, DATA_SIZE_FF); ff_frame.length = DATA_SIZE_FF + 2; data_ptr += DATA_SIZE_FF; remaining_length -= DATA_SIZE_FF; prepare_fc_frame(&fc_frame, FC_PCI_CTS, &fc_opts, true); prepare_cf_frames(des_frames, ARRAY_SIZE(des_frames), data_ptr, remaining_length, false); remaining_frames = DIV_ROUND_UP(remaining_length, DATA_SIZE_CF); filter_id = add_rx_msgq(tx_addr.std_id, CAN_STD_ID_MASK); zassert_true((filter_id >= 0), "Negative filter number [%d]", filter_id); ret = isotp_bind(&recv_ctx, can_dev, &rx_addr, &tx_addr, &fc_opts, K_NO_WAIT); zassert_equal(ret, ISOTP_N_OK, "Binding failed [%d]", ret); send_frame_series(&ff_frame, 1, rx_addr.std_id); while (remaining_frames) { check_frame_series(&fc_frame, 1, &frame_msgq); if (remaining_frames >= fc_opts.bs) { send_frame_series(data_frame_ptr, fc_opts.bs, rx_addr.std_id); data_frame_ptr += fc_opts.bs; remaining_frames -= fc_opts.bs; } else { send_frame_series(data_frame_ptr, remaining_frames, rx_addr.std_id); data_frame_ptr += remaining_frames; remaining_frames = 0; } } ret = k_msgq_get(&frame_msgq, &dummy_frame, K_MSEC(50)); zassert_equal(ret, -EAGAIN, "Expected timeout but got %d", ret); receive_test_data(random_data, DATA_SEND_LENGTH, 0); can_remove_rx_filter(can_dev, filter_id); isotp_unbind(&recv_ctx); } ZTEST(isotp_conformance, test_send_timeouts) { int ret; uint32_t start_time, time_diff; struct frame_desired fc_cts_frame; prepare_fc_frame(&fc_cts_frame, FC_PCI_CTS, &fc_opts, false); /* Test timeout for first FC*/ start_time = k_uptime_get_32(); ret = isotp_send(&send_ctx, can_dev, random_data, sizeof(random_data), &tx_addr, &rx_addr, NULL, NULL); time_diff = k_uptime_get_32() - start_time; zassert_equal(ret, ISOTP_N_TIMEOUT_BS, "Expected timeout but got %d", ret); zassert_true(time_diff <= BS_TIMEOUT_UPPER_MS, "Timeout too late (%dms)", time_diff); zassert_true(time_diff >= BS_TIMEOUT_LOWER_MS, "Timeout too early (%dms)", time_diff); /* Test timeout for consecutive FC frames */ k_sem_reset(&send_compl_sem); ret = isotp_send(&send_ctx, can_dev, random_data, sizeof(random_data), &tx_addr, &rx_addr, send_complete_cb, INT_TO_POINTER(ISOTP_N_TIMEOUT_BS)); zassert_equal(ret, ISOTP_N_OK, "Send returned %d", ret); send_frame_series(&fc_cts_frame, 1, rx_addr.std_id); start_time = k_uptime_get_32(); ret = k_sem_take(&send_compl_sem, K_MSEC(BS_TIMEOUT_UPPER_MS)); zassert_equal(ret, 0, "Timeout too late"); time_diff = k_uptime_get_32() - start_time; zassert_true(time_diff >= BS_TIMEOUT_LOWER_MS, "Timeout too early (%dms)", time_diff); /* Test timeout reset with WAIT frame */ k_sem_reset(&send_compl_sem); ret = isotp_send(&send_ctx, can_dev, random_data, sizeof(random_data), &tx_addr, &rx_addr, send_complete_cb, INT_TO_POINTER(ISOTP_N_TIMEOUT_BS)); zassert_equal(ret, ISOTP_N_OK, "Send returned %d", ret); ret = k_sem_take(&send_compl_sem, K_MSEC(800)); zassert_equal(ret, -EAGAIN, "Timeout too early"); fc_cts_frame.data[0] = FC_PCI_BYTE_1(FC_PCI_CTS); send_frame_series(&fc_cts_frame, 1, rx_addr.std_id); start_time = k_uptime_get_32(); ret = k_sem_take(&send_compl_sem, K_MSEC(BS_TIMEOUT_UPPER_MS)); zassert_equal(ret, 0, "Timeout too late"); time_diff = k_uptime_get_32() - start_time; zassert_true(time_diff >= BS_TIMEOUT_LOWER_MS, "Timeout too early (%dms)", time_diff); } ZTEST(isotp_conformance, test_receive_timeouts) { int ret; uint32_t start_time, time_diff; struct frame_desired ff_frame; ff_frame.data[0] = FF_PCI_BYTE_1(DATA_SEND_LENGTH); ff_frame.data[1] = FF_PCI_BYTE_2(DATA_SEND_LENGTH); memcpy(&ff_frame.data[2], random_data, DATA_SIZE_FF); ff_frame.length = DATA_SIZE_FF + 2; ret = isotp_bind(&recv_ctx, can_dev, &rx_addr, &tx_addr, &fc_opts, K_NO_WAIT); zassert_equal(ret, ISOTP_N_OK, "Binding failed [%d]", ret); send_frame_series(&ff_frame, 1, rx_addr.std_id); start_time = k_uptime_get_32(); ret = isotp_recv(&recv_ctx, data_buf, sizeof(data_buf), K_FOREVER); zassert_equal(ret, DATA_SIZE_FF, "Expected FF data length but got %d", ret); ret = isotp_recv(&recv_ctx, data_buf, sizeof(data_buf), K_FOREVER); zassert_equal(ret, ISOTP_N_TIMEOUT_CR, "Expected timeout but got %d", ret); time_diff = k_uptime_get_32() - start_time; zassert_true(time_diff >= BS_TIMEOUT_LOWER_MS, "Timeout too early (%dms)", time_diff); zassert_true(time_diff <= BS_TIMEOUT_UPPER_MS, "Timeout too slow (%dms)", time_diff); isotp_unbind(&recv_ctx); } ZTEST(isotp_conformance, test_stmin) { int filter_id, ret; struct frame_desired fc_frame, ff_frame; struct can_frame raw_frame; uint32_t start_time, time_diff; struct isotp_fc_opts fc_opts_stmin = { .bs = 2, .stmin = STMIN_VAL_1 }; if (CONFIG_SYS_CLOCK_TICKS_PER_SEC < 1000) { /* This test requires millisecond tick resolution */ ztest_test_skip(); } ff_frame.data[0] = FF_PCI_BYTE_1(DATA_SIZE_FF + DATA_SIZE_CF * 4); ff_frame.data[1] = FF_PCI_BYTE_2(DATA_SIZE_FF + DATA_SIZE_CF * 4); memcpy(&ff_frame.data[2], random_data, DATA_SIZE_FF); ff_frame.length = DATA_SIZE_FF + 2; prepare_fc_frame(&fc_frame, FC_PCI_CTS, &fc_opts_stmin, false); filter_id = add_rx_msgq(rx_addr.std_id, CAN_STD_ID_MASK); zassert_true((filter_id >= 0), "Negative filter number [%d]", filter_id); send_test_data(random_data, DATA_SIZE_FF + DATA_SIZE_CF * 4); check_frame_series(&ff_frame, 1, &frame_msgq); send_frame_series(&fc_frame, 1, tx_addr.std_id); ret = k_msgq_get(&frame_msgq, &raw_frame, K_MSEC(100)); zassert_equal(ret, 0, "Expected to get a message. [%d]", ret); start_time = k_uptime_get_32(); ret = k_msgq_get(&frame_msgq, &raw_frame, K_MSEC(STMIN_VAL_1 + STMIN_UPPER_TOLERANCE)); time_diff = k_uptime_get_32() - start_time; zassert_equal(ret, 0, "Expected to get a message within %dms. [%d]", STMIN_VAL_1 + STMIN_UPPER_TOLERANCE, ret); zassert_true(time_diff >= STMIN_VAL_1, "STmin too short (%dms)", time_diff); fc_frame.data[2] = FC_PCI_BYTE_3(STMIN_VAL_2); send_frame_series(&fc_frame, 1, tx_addr.std_id); ret = k_msgq_get(&frame_msgq, &raw_frame, K_MSEC(100)); zassert_equal(ret, 0, "Expected to get a message. [%d]", ret); start_time = k_uptime_get_32(); ret = k_msgq_get(&frame_msgq, &raw_frame, K_MSEC(STMIN_VAL_2 + STMIN_UPPER_TOLERANCE)); time_diff = k_uptime_get_32() - start_time; zassert_equal(ret, 0, "Expected to get a message within %dms. [%d]", STMIN_VAL_2 + STMIN_UPPER_TOLERANCE, ret); zassert_true(time_diff >= STMIN_VAL_2, "STmin too short (%dms)", time_diff); can_remove_rx_filter(can_dev, filter_id); } ZTEST(isotp_conformance, test_receiver_fc_errors) { int ret, filter_id; struct frame_desired ff_frame, fc_frame; ff_frame.data[0] = FF_PCI_BYTE_1(DATA_SEND_LENGTH); ff_frame.data[1] = FF_PCI_BYTE_2(DATA_SEND_LENGTH); memcpy(&ff_frame.data[2], random_data, DATA_SIZE_FF); ff_frame.length = DATA_SIZE_FF + 2; prepare_fc_frame(&fc_frame, FC_PCI_CTS, &fc_opts, true); filter_id = add_rx_msgq(tx_addr.std_id, CAN_STD_ID_MASK); zassert_true((filter_id >= 0), "Negative filter number [%d]", filter_id); ret = isotp_bind(&recv_ctx, can_dev, &rx_addr, &tx_addr, &fc_opts, K_NO_WAIT); zassert_equal(ret, ISOTP_N_OK, "Binding failed [%d]", ret); /* wrong sequence number */ send_frame_series(&ff_frame, 1, rx_addr.std_id); check_frame_series(&fc_frame, 1, &frame_msgq); prepare_cf_frames(des_frames, ARRAY_SIZE(des_frames), random_data + DATA_SIZE_FF, sizeof(random_data) - DATA_SIZE_FF, false); /* SN should be 2 but is set to 3 for this test */ des_frames[1].data[0] = CF_PCI_BYTE_1 | (3 & 0x0F); send_frame_series(des_frames, fc_opts.bs, rx_addr.std_id); ret = isotp_recv(&recv_ctx, data_buf, sizeof(data_buf), K_MSEC(200)); zassert_equal(ret, DATA_SIZE_FF, "Expected FF data length but got %d", ret); ret = isotp_recv(&recv_ctx, data_buf, sizeof(data_buf), K_MSEC(200)); zassert_equal(ret, ISOTP_N_WRONG_SN, "Expected wrong SN but got %d", ret); /* buffer overflow */ ff_frame.data[0] = FF_PCI_BYTE_1(0xFFF); ff_frame.data[1] = FF_PCI_BYTE_2(0xFFF); fc_frame.data[0] = FC_PCI_BYTE_1(FC_PCI_OVFLW); fc_frame.data[1] = FC_PCI_BYTE_2(0); fc_frame.data[2] = FC_PCI_BYTE_3(0); isotp_unbind(&recv_ctx); ret = isotp_bind(&recv_ctx, can_dev, &rx_addr, &tx_addr, &fc_opts_single, K_NO_WAIT); zassert_equal(ret, ISOTP_N_OK, "Binding failed [%d]", ret); send_frame_series(&ff_frame, 1, rx_addr.std_id); check_frame_series(&fc_frame, 1, &frame_msgq); can_remove_rx_filter(can_dev, filter_id); k_msgq_cleanup(&frame_msgq); isotp_unbind(&recv_ctx); } ZTEST(isotp_conformance, test_sender_fc_errors) { int ret, filter_id, i; struct frame_desired ff_frame, fc_frame; ff_frame.data[0] = FF_PCI_BYTE_1(DATA_SEND_LENGTH); ff_frame.data[1] = FF_PCI_BYTE_2(DATA_SEND_LENGTH); memcpy(&ff_frame.data[2], random_data, DATA_SIZE_FF); ff_frame.length = DATA_SIZE_FF + 2; filter_id = add_rx_msgq(tx_addr.std_id, CAN_STD_ID_MASK); /* invalid flow status */ prepare_fc_frame(&fc_frame, 3, &fc_opts, false); k_sem_reset(&send_compl_sem); ret = isotp_send(&send_ctx, can_dev, random_data, DATA_SEND_LENGTH, &tx_addr, &rx_addr, send_complete_cb, INT_TO_POINTER(ISOTP_N_INVALID_FS)); zassert_equal(ret, ISOTP_N_OK, "Send returned %d", ret); check_frame_series(&ff_frame, 1, &frame_msgq); send_frame_series(&fc_frame, 1, rx_addr.std_id); ret = k_sem_take(&send_compl_sem, K_MSEC(200)); zassert_equal(ret, 0, "Send complete callback not called"); /* buffer overflow */ k_sem_reset(&send_compl_sem); ret = isotp_send(&send_ctx, can_dev, random_data, DATA_SEND_LENGTH, &tx_addr, &rx_addr, send_complete_cb, INT_TO_POINTER(ISOTP_N_BUFFER_OVERFLW)); check_frame_series(&ff_frame, 1, &frame_msgq); fc_frame.data[0] = FC_PCI_BYTE_1(FC_PCI_OVFLW); send_frame_series(&fc_frame, 1, rx_addr.std_id); ret = k_sem_take(&send_compl_sem, K_MSEC(200)); zassert_equal(ret, 0, "Send complete callback not called"); /* wft overrun */ k_sem_reset(&send_compl_sem); ret = isotp_send(&send_ctx, can_dev, random_data, DATA_SEND_LENGTH, &tx_addr, &rx_addr, send_complete_cb, INT_TO_POINTER(ISOTP_N_WFT_OVRN)); check_frame_series(&ff_frame, 1, &frame_msgq); fc_frame.data[0] = FC_PCI_BYTE_1(FC_PCI_WAIT); for (i = 0; i < CONFIG_ISOTP_WFTMAX + 1; i++) { send_frame_series(&fc_frame, 1, rx_addr.std_id); } ret = k_sem_take(&send_compl_sem, K_MSEC(200)); zassert_equal(ret, 0, "Send complete callback not called"); k_msgq_cleanup(&frame_msgq); can_remove_rx_filter(can_dev, filter_id); } ZTEST(isotp_conformance, test_canfd_mandatory_padding) { /* Mandatory padding of CAN FD frames (TX_DL > 8). * Must be padded with 0xCC up to the nearest DLC. */ #if TX_DL < 12 ztest_test_skip(); #else /* Input a single frame packet of 10 bytes */ uint8_t data_size_sf = 10 - 2; int filter_id, ret; struct can_frame frame = {}; const uint8_t expected_padding[] = { 0xCC, 0xCC }; filter_id = add_rx_msgq(rx_addr.std_id, CAN_STD_ID_MASK); ret = isotp_send(&send_ctx, can_dev, random_data, data_size_sf, &rx_addr, &tx_addr, send_complete_cb, INT_TO_POINTER(ISOTP_N_OK)); zassert_equal(ret, 0, "Send returned %d", ret); ret = k_msgq_get(&frame_msgq, &frame, K_MSEC(500)); zassert_equal(ret, 0, "Timeout waiting for msg. ret: %d", ret); /* The output frame should be 12 bytes, with the last two bytes being 0xCC */ zassert_equal(can_dlc_to_bytes(frame.dlc), 12, "Incorrect DLC"); zassert_mem_equal(&frame.data[10], expected_padding, sizeof(expected_padding)); can_remove_rx_filter(can_dev, filter_id); #endif } ZTEST(isotp_conformance, test_canfd_rx_dl_validation) { /* First frame defines the RX data length, consecutive frames * must have the same length (except the last frame) */ #if TX_DL < 16 ztest_test_skip(); #else uint8_t data_size_ff = 16 - 2; uint8_t data_size_cf = 12 - 1; uint8_t data_send_length = data_size_ff + 2 * data_size_cf; const uint8_t *data_ptr = random_data; int filter_id, ret; struct frame_desired fc_frame, ff_frame; /* FF uses a TX_DL of 16 */ ff_frame.data[0] = FF_PCI_BYTE_1(data_send_length); ff_frame.data[1] = FF_PCI_BYTE_2(data_send_length); memcpy(&ff_frame.data[2], data_ptr, data_size_ff); ff_frame.length = data_size_ff + 2; data_ptr += data_size_ff; prepare_fc_frame(&fc_frame, FC_PCI_CTS, &fc_opts_single, true); /* Two CF frames using a TX_DL of 12 */ des_frames[0].data[0] = CF_PCI_BYTE_1 | (1 & 0x0F); des_frames[0].length = data_size_cf + 1; memcpy(&des_frames[0].data[1], data_ptr, data_size_cf); data_ptr += data_size_cf; des_frames[1].data[0] = CF_PCI_BYTE_1 | (2 & 0x0F); des_frames[1].length = data_size_cf + 1; memcpy(&des_frames[1].data[1], data_ptr, data_size_cf); data_ptr += data_size_cf; filter_id = add_rx_msgq(tx_addr.std_id, CAN_STD_ID_MASK); ret = isotp_bind(&recv_ctx, can_dev, &rx_addr, &tx_addr, &fc_opts_single, K_NO_WAIT); zassert_equal(ret, ISOTP_N_OK, "Binding failed [%d]", ret); send_frame_series(&ff_frame, 1, rx_addr.std_id); check_frame_series(&fc_frame, 1, &frame_msgq); send_frame_series(des_frames, 2, rx_addr.std_id); /* Assert that the packet was dropped and an error returned */ ret = isotp_recv(&recv_ctx, data_buf, sizeof(data_buf), K_MSEC(200)); zassert_equal(ret, ISOTP_N_ERROR, "recv returned %d", ret); can_remove_rx_filter(can_dev, filter_id); isotp_unbind(&recv_ctx); #endif } static bool canfd_predicate(const void *state) { ARG_UNUSED(state); #ifdef CONFIG_TEST_USE_CAN_FD_MODE can_mode_t cap; int err; err = can_get_capabilities(can_dev, &cap); zassert_equal(err, 0, "failed to get CAN controller capabilities (err %d)", err); if ((cap & CAN_MODE_FD) == 0) { return false; } #endif return true; } static void *isotp_conformance_setup(void) { int ret; zassert_true(sizeof(random_data) >= sizeof(data_buf) * 2 + 10, "Test data size to small"); zassert_true(device_is_ready(can_dev), "CAN device not ready"); (void)can_stop(can_dev); ret = can_set_mode(can_dev, CAN_MODE_LOOPBACK | (IS_ENABLED(CONFIG_TEST_USE_CAN_FD_MODE) ? CAN_MODE_FD : 0)); zassert_equal(ret, 0, "Failed to set mode [%d]", ret); ret = can_start(can_dev); zassert_equal(ret, 0, "Failed to start CAN controller [%d]", ret); k_sem_init(&send_compl_sem, 0, 1); return NULL; } ZTEST_SUITE(isotp_conformance, canfd_predicate, isotp_conformance_setup, NULL, NULL, NULL);