blob: bd4a7b52de2dadd8277b22f3ac23b4d71f21247a [file] [log] [blame]
/*
* Copyright (c) 2019 Alexander Wachter
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/canbus/isotp.h>
#include <zephyr/drivers/can.h>
#include <zephyr/sys/util.h>
#include <zephyr/ztest.h>
#include <strings.h>
#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);