You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
629 lines
14 KiB
629 lines
14 KiB
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <asm/termbits.h> /* struct termios2 */
|
|
#include <time.h>
|
|
#include <ctype.h>
|
|
#include <signal.h>
|
|
#include <sys/time.h>
|
|
|
|
#define CANUSB_INJECT_SLEEP_GAP_DEFAULT 200 /* ms */
|
|
#define CANUSB_TTY_BAUD_RATE_DEFAULT 2000000
|
|
|
|
typedef enum {
|
|
CANUSB_SPEED_1000000 = 0x01,
|
|
CANUSB_SPEED_800000 = 0x02,
|
|
CANUSB_SPEED_500000 = 0x03,
|
|
CANUSB_SPEED_400000 = 0x04,
|
|
CANUSB_SPEED_250000 = 0x05,
|
|
CANUSB_SPEED_200000 = 0x06,
|
|
CANUSB_SPEED_125000 = 0x07,
|
|
CANUSB_SPEED_100000 = 0x08,
|
|
CANUSB_SPEED_50000 = 0x09,
|
|
CANUSB_SPEED_20000 = 0x0a,
|
|
CANUSB_SPEED_10000 = 0x0b,
|
|
CANUSB_SPEED_5000 = 0x0c,
|
|
} CANUSB_SPEED;
|
|
|
|
typedef enum {
|
|
CANUSB_MODE_NORMAL = 0x00,
|
|
CANUSB_MODE_LOOPBACK = 0x01,
|
|
CANUSB_MODE_SILENT = 0x02,
|
|
CANUSB_MODE_LOOPBACK_SILENT = 0x03,
|
|
} CANUSB_MODE;
|
|
|
|
typedef enum {
|
|
CANUSB_FRAME_STANDARD = 0x01,
|
|
CANUSB_FRAME_EXTENDED = 0x02,
|
|
} CANUSB_FRAME;
|
|
|
|
typedef enum {
|
|
CANUSB_INJECT_PAYLOAD_MODE_RANDOM = 0,
|
|
CANUSB_INJECT_PAYLOAD_MODE_INCREMENTAL = 1,
|
|
CANUSB_INJECT_PAYLOAD_MODE_FIXED = 2,
|
|
} CANUSB_PAYLOAD_MODE;
|
|
|
|
|
|
|
|
static int terminate_after = 0;
|
|
static int program_running = 1;
|
|
static int inject_payload_mode = CANUSB_INJECT_PAYLOAD_MODE_FIXED;
|
|
static float inject_sleep_gap = CANUSB_INJECT_SLEEP_GAP_DEFAULT;
|
|
static int print_traffic = 0;
|
|
|
|
|
|
|
|
static CANUSB_SPEED canusb_int_to_speed(int speed)
|
|
{
|
|
switch (speed) {
|
|
case 1000000:
|
|
return CANUSB_SPEED_1000000;
|
|
case 800000:
|
|
return CANUSB_SPEED_800000;
|
|
case 500000:
|
|
return CANUSB_SPEED_500000;
|
|
case 400000:
|
|
return CANUSB_SPEED_400000;
|
|
case 250000:
|
|
return CANUSB_SPEED_250000;
|
|
case 200000:
|
|
return CANUSB_SPEED_200000;
|
|
case 125000:
|
|
return CANUSB_SPEED_125000;
|
|
case 100000:
|
|
return CANUSB_SPEED_100000;
|
|
case 50000:
|
|
return CANUSB_SPEED_50000;
|
|
case 20000:
|
|
return CANUSB_SPEED_20000;
|
|
case 10000:
|
|
return CANUSB_SPEED_10000;
|
|
case 5000:
|
|
return CANUSB_SPEED_5000;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static int generate_checksum(const unsigned char *data, int data_len)
|
|
{
|
|
int i, checksum;
|
|
|
|
checksum = 0;
|
|
for (i = 0; i < data_len; i++) {
|
|
checksum += data[i];
|
|
}
|
|
|
|
return checksum & 0xff;
|
|
}
|
|
|
|
|
|
|
|
static int frame_is_complete(const unsigned char *frame, int frame_len)
|
|
{
|
|
if (frame_len > 0) {
|
|
if (frame[0] != 0xaa) {
|
|
/* Need to sync on 0xaa at start of frames, so just skip. */
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if (frame_len < 2) {
|
|
return 0;
|
|
}
|
|
|
|
if (frame[1] == 0x55) { /* Command frame... */
|
|
if (frame_len >= 20) { /* ...always 20 bytes. */
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
} else if ((frame[1] >> 4) == 0xc) { /* Data frame... */
|
|
if (frame_len >= (frame[1] & 0xf) + 5) { /* ...payload and 5 bytes. */
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* Unhandled frame type. */
|
|
return 1;
|
|
}
|
|
|
|
|
|
|
|
static int frame_send(int tty_fd, const unsigned char *frame, int frame_len)
|
|
{
|
|
int result, i;
|
|
|
|
if (print_traffic) {
|
|
printf(">>> ");
|
|
for (i = 0; i < frame_len; i++) {
|
|
printf("%02x ", frame[i]);
|
|
}
|
|
if (print_traffic > 1) {
|
|
printf(" '");
|
|
for (i = 4; i < frame_len - 1; i++) {
|
|
printf("%c", isalnum(frame[i]) ? frame[i] : '.');
|
|
}
|
|
printf("'");
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
result = write(tty_fd, frame, frame_len);
|
|
if (result == -1) {
|
|
fprintf(stderr, "write() failed: %s\n", strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
return frame_len;
|
|
}
|
|
|
|
|
|
|
|
static int frame_recv(int tty_fd, unsigned char *frame, int frame_len_max)
|
|
{
|
|
int result, frame_len, checksum;
|
|
unsigned char byte;
|
|
|
|
if (print_traffic)
|
|
fprintf(stderr, "<<< ");
|
|
|
|
frame_len = 0;
|
|
while (program_running) {
|
|
result = read(tty_fd, &byte, 1);
|
|
if (result == -1) {
|
|
if (errno != EAGAIN && errno != EWOULDBLOCK) {
|
|
fprintf(stderr, "read() failed: %s\n", strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
} else if (result > 0) {
|
|
if (print_traffic)
|
|
fprintf(stderr, "%02x ", byte);
|
|
|
|
if (frame_len == frame_len_max) {
|
|
fprintf(stderr, "frame_recv() failed: Overflow\n");
|
|
return -1;
|
|
}
|
|
|
|
frame[frame_len++] = byte;
|
|
|
|
if (frame_is_complete(frame, frame_len)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
usleep(10);
|
|
}
|
|
|
|
if (print_traffic)
|
|
fprintf(stderr, "\n");
|
|
|
|
/* Compare checksum for command frames only. */
|
|
if ((frame_len == 20) && (frame[0] == 0xaa) && (frame[1] == 0x55)) {
|
|
checksum = generate_checksum(&frame[2], 17);
|
|
if (checksum != frame[frame_len - 1]) {
|
|
fprintf(stderr, "frame_recv() failed: Checksum incorrect\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return frame_len;
|
|
}
|
|
|
|
|
|
|
|
static int command_settings(int tty_fd, CANUSB_SPEED speed, CANUSB_MODE mode, CANUSB_FRAME frame)
|
|
{
|
|
int cmd_frame_len;
|
|
unsigned char cmd_frame[20];
|
|
|
|
cmd_frame_len = 0;
|
|
cmd_frame[cmd_frame_len++] = 0xaa;
|
|
cmd_frame[cmd_frame_len++] = 0x55;
|
|
cmd_frame[cmd_frame_len++] = 0x12;
|
|
cmd_frame[cmd_frame_len++] = speed;
|
|
cmd_frame[cmd_frame_len++] = frame;
|
|
cmd_frame[cmd_frame_len++] = 0; /* Filter ID not handled. */
|
|
cmd_frame[cmd_frame_len++] = 0; /* Filter ID not handled. */
|
|
cmd_frame[cmd_frame_len++] = 0; /* Filter ID not handled. */
|
|
cmd_frame[cmd_frame_len++] = 0; /* Filter ID not handled. */
|
|
cmd_frame[cmd_frame_len++] = 0; /* Mask ID not handled. */
|
|
cmd_frame[cmd_frame_len++] = 0; /* Mask ID not handled. */
|
|
cmd_frame[cmd_frame_len++] = 0; /* Mask ID not handled. */
|
|
cmd_frame[cmd_frame_len++] = 0; /* Mask ID not handled. */
|
|
cmd_frame[cmd_frame_len++] = mode;
|
|
cmd_frame[cmd_frame_len++] = 0x01;
|
|
cmd_frame[cmd_frame_len++] = 0;
|
|
cmd_frame[cmd_frame_len++] = 0;
|
|
cmd_frame[cmd_frame_len++] = 0;
|
|
cmd_frame[cmd_frame_len++] = 0;
|
|
cmd_frame[cmd_frame_len++] = generate_checksum(&cmd_frame[2], 17);
|
|
|
|
if (frame_send(tty_fd, cmd_frame, cmd_frame_len) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
static int send_data_frame(int tty_fd, CANUSB_FRAME frame, unsigned char id_lsb, unsigned char id_msb, unsigned char data[], int data_length_code)
|
|
{
|
|
#define MAX_FRAME_SIZE 13
|
|
int data_frame_len = 0;
|
|
unsigned char data_frame[MAX_FRAME_SIZE] = {0x00};
|
|
|
|
if (data_length_code < 0 || data_length_code > 8)
|
|
{
|
|
fprintf(stderr, "Data length code (DLC) must be between 0 and 8!\n");
|
|
return -1;
|
|
}
|
|
|
|
/* Byte 0: Packet Start */
|
|
data_frame[data_frame_len++] = 0xaa;
|
|
|
|
/* Byte 1: CAN Bus Data Frame Information */
|
|
data_frame[data_frame_len] = 0x00;
|
|
data_frame[data_frame_len] |= 0xC0; /* Bit 7 Always 1, Bit 6 Always 1 */
|
|
if (frame == CANUSB_FRAME_STANDARD)
|
|
data_frame[data_frame_len] &= 0xDF; /* STD frame */
|
|
else /* CANUSB_FRAME_EXTENDED */
|
|
data_frame[data_frame_len] |= 0x20; /* EXT frame */
|
|
data_frame[data_frame_len] &= 0xEF; /* 0=Data */
|
|
data_frame[data_frame_len] |= data_length_code; /* DLC=data_len */
|
|
data_frame_len++;
|
|
|
|
/* Byte 2 to 3: ID */
|
|
data_frame[data_frame_len++] = id_lsb; /* lsb */
|
|
data_frame[data_frame_len++] = id_msb; /* msb */
|
|
|
|
/* Byte 4 to (4+data_len): Data */
|
|
for (int i = 0; i < data_length_code; i++)
|
|
data_frame[data_frame_len++] = data[i];
|
|
|
|
/* Last byte: End of frame */
|
|
data_frame[data_frame_len++] = 0x55;
|
|
|
|
if (frame_send(tty_fd, data_frame, data_frame_len) < 0)
|
|
{
|
|
fprintf(stderr, "Unable to send frame!\n");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
static int hex_value(int c)
|
|
{
|
|
if (c >= 0x30 && c <= 0x39) /* '0' - '9' */
|
|
return c - 0x30;
|
|
else if (c >= 0x41 && c <= 0x46) /* 'A' - 'F' */
|
|
return (c - 0x41) + 10;
|
|
else if (c >= 0x61 && c <= 0x66) /* 'a' - 'f' */
|
|
return (c - 0x61) + 10;
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
|
|
|
|
static int convert_from_hex(const char *hex_string, unsigned char *bin_string, int bin_string_len)
|
|
{
|
|
int n1, n2, high;
|
|
|
|
high = -1;
|
|
n1 = n2 = 0;
|
|
while (hex_string[n1] != '\0') {
|
|
if (hex_value(hex_string[n1]) >= 0) {
|
|
if (high == -1) {
|
|
high = hex_string[n1];
|
|
} else {
|
|
bin_string[n2] = hex_value(high) * 16 + hex_value(hex_string[n1]);
|
|
n2++;
|
|
if (n2 >= bin_string_len) {
|
|
printf("hex string truncated to %d bytes\n", n2);
|
|
break;
|
|
}
|
|
high = -1;
|
|
}
|
|
}
|
|
n1++;
|
|
}
|
|
|
|
return n2;
|
|
}
|
|
|
|
|
|
|
|
static int inject_data_frame(int tty_fd, const char *hex_id, const char *hex_data)
|
|
{
|
|
int data_len;
|
|
unsigned char binary_data[8];
|
|
unsigned char binary_id_lsb = 0, binary_id_msb = 0;
|
|
struct timespec gap_ts;
|
|
struct timeval now;
|
|
int error = 0;
|
|
|
|
gap_ts.tv_sec = inject_sleep_gap / 1000;
|
|
gap_ts.tv_nsec = (long)(((long long)(inject_sleep_gap * 1000000)) % 1000000000LL);
|
|
|
|
/* Set seed value for pseudo random numbers. */
|
|
gettimeofday(&now, NULL);
|
|
srandom(now.tv_usec);
|
|
|
|
data_len = convert_from_hex(hex_data, binary_data, sizeof(binary_data));
|
|
if (data_len == 0) {
|
|
fprintf(stderr, "Unable to convert data from hex to binary!\n");
|
|
return -1;
|
|
}
|
|
|
|
switch (strlen(hex_id)) {
|
|
case 1:
|
|
binary_id_lsb = hex_value(hex_id[0]);
|
|
break;
|
|
|
|
case 2:
|
|
binary_id_lsb = (hex_value(hex_id[0]) * 16) + hex_value(hex_id[1]);
|
|
break;
|
|
|
|
case 3:
|
|
binary_id_msb = hex_value(hex_id[0]);
|
|
binary_id_lsb = (hex_value(hex_id[1]) * 16) + hex_value(hex_id[2]);
|
|
break;
|
|
|
|
default:
|
|
fprintf(stderr, "Unable to convert ID from hex to binary!\n");
|
|
return -1;
|
|
}
|
|
|
|
while (program_running && ! error) {
|
|
if (gap_ts.tv_sec || gap_ts.tv_nsec)
|
|
nanosleep(&gap_ts, NULL);
|
|
|
|
if (terminate_after && (--terminate_after == 0))
|
|
program_running = 0;
|
|
|
|
if (inject_payload_mode == CANUSB_INJECT_PAYLOAD_MODE_RANDOM) {
|
|
int i;
|
|
for (i = 0; i < data_len; i++)
|
|
binary_data[i] = random();
|
|
} else if (inject_payload_mode == CANUSB_INJECT_PAYLOAD_MODE_INCREMENTAL) {
|
|
int i;
|
|
for (i = 0; i < data_len; i++)
|
|
binary_data[i]++;
|
|
}
|
|
|
|
error = send_data_frame(tty_fd, CANUSB_FRAME_STANDARD, binary_id_lsb, binary_id_msb, binary_data, data_len);
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
|
|
static void dump_data_frames(int tty_fd)
|
|
{
|
|
int i, frame_len;
|
|
unsigned char frame[32];
|
|
struct timespec ts;
|
|
|
|
while (program_running) {
|
|
frame_len = frame_recv(tty_fd, frame, sizeof(frame));
|
|
|
|
if (! program_running)
|
|
break;
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
printf("%lu.%06lu ", ts.tv_sec, ts.tv_nsec / 1000);
|
|
|
|
if (frame_len == -1) {
|
|
printf("Frame recieve error!\n");
|
|
|
|
} else {
|
|
|
|
if ((frame_len >= 6) &&
|
|
(frame[0] == 0xaa) &&
|
|
((frame[1] >> 4) == 0xc)) {
|
|
printf("Frame ID: %02x%02x, Data: ", frame[3], frame[2]);
|
|
for (i = frame_len - 2; i > 3; i--) {
|
|
printf("%02x ", frame[i]);
|
|
}
|
|
printf("\n");
|
|
|
|
} else {
|
|
printf("Unknown: ");
|
|
for (i = 0; i <= frame_len; i++) {
|
|
printf("%02x ", frame[i]);
|
|
}
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
if (terminate_after && (--terminate_after == 0))
|
|
program_running = 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static int adapter_init(const char *tty_device, int baudrate)
|
|
{
|
|
int tty_fd, result;
|
|
struct termios2 tio;
|
|
|
|
tty_fd = open(tty_device, O_RDWR | O_NOCTTY | O_NONBLOCK);
|
|
if (tty_fd == -1) {
|
|
fprintf(stderr, "open(%s) failed: %s\n", tty_device, strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
result = ioctl(tty_fd, TCGETS2, &tio);
|
|
if (result == -1) {
|
|
fprintf(stderr, "ioctl() failed: %s\n", strerror(errno));
|
|
close(tty_fd);
|
|
return -1;
|
|
}
|
|
|
|
tio.c_cflag &= ~CBAUD;
|
|
tio.c_cflag = BOTHER | CS8 | CSTOPB;
|
|
tio.c_iflag = IGNPAR;
|
|
tio.c_oflag = 0;
|
|
tio.c_lflag = 0;
|
|
tio.c_ispeed = baudrate;
|
|
tio.c_ospeed = baudrate;
|
|
|
|
result = ioctl(tty_fd, TCSETS2, &tio);
|
|
if (result == -1) {
|
|
fprintf(stderr, "ioctl() failed: %s\n", strerror(errno));
|
|
close(tty_fd);
|
|
return -1;
|
|
}
|
|
|
|
return tty_fd;
|
|
}
|
|
|
|
|
|
|
|
static void display_help(const char *progname)
|
|
{
|
|
fprintf(stderr, "Usage: %s <options>\n", progname);
|
|
fprintf(stderr, "Options:\n"
|
|
" -h Display this help and exit.\n"
|
|
" -t Print TTY/serial traffic debugging info on stderr.\n"
|
|
" -d DEVICE Use TTY DEVICE.\n"
|
|
" -s SPEED Set CAN SPEED in bps.\n"
|
|
" -b BAUDRATE Set TTY/serial BAUDRATE (default: %d).\n"
|
|
" -i ID Inject using ID (specified as hex string).\n"
|
|
" -j DATA CAN DATA to inject (specified as hex string).\n"
|
|
" -n COUNT Terminate after COUNT frames (default: infinite).\n"
|
|
" -g MS Inject sleep gap in MS milliseconds (default: %d ms).\n"
|
|
" -m MODE Inject payload MODE (%d = random, %d = incremental, %d = fixed).\n"
|
|
"\n",
|
|
CANUSB_TTY_BAUD_RATE_DEFAULT,
|
|
CANUSB_INJECT_SLEEP_GAP_DEFAULT,
|
|
CANUSB_INJECT_PAYLOAD_MODE_RANDOM,
|
|
CANUSB_INJECT_PAYLOAD_MODE_INCREMENTAL,
|
|
CANUSB_INJECT_PAYLOAD_MODE_FIXED);
|
|
}
|
|
|
|
|
|
|
|
static void sigterm(int signo)
|
|
{
|
|
program_running = 0;
|
|
}
|
|
|
|
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
int c, tty_fd;
|
|
char *tty_device = NULL, *inject_data = NULL, *inject_id = NULL;
|
|
CANUSB_SPEED speed = 0;
|
|
int baudrate = CANUSB_TTY_BAUD_RATE_DEFAULT;
|
|
|
|
while ((c = getopt(argc, argv, "htd:s:b:i:j:n:g:m:")) != -1) {
|
|
switch (c) {
|
|
case 'h':
|
|
display_help(argv[0]);
|
|
return EXIT_SUCCESS;
|
|
|
|
case 't':
|
|
print_traffic++;
|
|
break;
|
|
|
|
case 'd':
|
|
tty_device = optarg;
|
|
break;
|
|
|
|
case 's':
|
|
speed = canusb_int_to_speed(atoi(optarg));
|
|
break;
|
|
|
|
case 'b':
|
|
baudrate = atoi(optarg);
|
|
break;
|
|
|
|
case 'i':
|
|
inject_id = optarg;
|
|
break;
|
|
|
|
case 'j':
|
|
inject_data = optarg;
|
|
break;
|
|
|
|
case 'n':
|
|
terminate_after = atoi(optarg);
|
|
break;
|
|
|
|
case 'g':
|
|
inject_sleep_gap = strtof(optarg, NULL);
|
|
break;
|
|
|
|
case 'm':
|
|
inject_payload_mode = atoi(optarg);
|
|
break;
|
|
|
|
case '?':
|
|
default:
|
|
display_help(argv[0]);
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
|
|
signal(SIGTERM, sigterm);
|
|
signal(SIGHUP, sigterm);
|
|
signal(SIGINT, sigterm);
|
|
|
|
if (tty_device == NULL) {
|
|
fprintf(stderr, "Please specify a TTY!\n");
|
|
display_help(argv[0]);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
if (speed == 0) {
|
|
fprintf(stderr, "Please specify a valid speed!\n");
|
|
display_help(argv[0]);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
tty_fd = adapter_init(tty_device, baudrate);
|
|
if (tty_fd == -1) {
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
command_settings(tty_fd, speed, CANUSB_MODE_NORMAL, CANUSB_FRAME_STANDARD);
|
|
|
|
if (inject_data == NULL) {
|
|
/* Dumping mode (default). */
|
|
dump_data_frames(tty_fd);
|
|
} else {
|
|
/* Inject mode. */
|
|
if (inject_id == NULL) {
|
|
fprintf(stderr, "Please specify a ID for injection!\n");
|
|
display_help(argv[0]);
|
|
return EXIT_FAILURE;
|
|
}
|
|
if (inject_data_frame(tty_fd, inject_id, inject_data) == -1) {
|
|
return EXIT_FAILURE;
|
|
} else {
|
|
return EXIT_SUCCESS;
|
|
}
|
|
}
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|