diff options
-rw-r--r-- | wiretap/CMakeLists.txt | 4 | ||||
-rw-r--r-- | wiretap/busmaster.c | 446 | ||||
-rw-r--r-- | wiretap/busmaster.h | 20 | ||||
-rw-r--r-- | wiretap/busmaster_parser.lemon | 453 | ||||
-rw-r--r-- | wiretap/busmaster_priv.h | 136 | ||||
-rw-r--r-- | wiretap/busmaster_scanner.l | 198 | ||||
-rw-r--r-- | wiretap/candump_priv.h | 22 | ||||
-rw-r--r-- | wiretap/file_access.c | 2 | ||||
-rw-r--r-- | wiretap/socketcan.h | 38 |
9 files changed, 1298 insertions, 21 deletions
diff --git a/wiretap/CMakeLists.txt b/wiretap/CMakeLists.txt index 319e677f93..720aab05ab 100644 --- a/wiretap/CMakeLists.txt +++ b/wiretap/CMakeLists.txt @@ -24,6 +24,7 @@ set(WIRETAP_NONGENERATED_FILES atm.c ber.c btsnoop.c + busmaster.c camins.c candump.c capsa.c @@ -89,6 +90,7 @@ set(WIRETAP_FILES ${WIRETAP_NONGENERATED_FILES}) add_lex_files(LEX_FILES WIRETAP_FILES ascend_scanner.l + busmaster_scanner.l candump_scanner.l k12text.l ) @@ -98,6 +100,7 @@ add_yacc_files(YACC_FILES WIRETAP_FILES ) add_lemon_files(LEMON_FILES WIRETAP_FILES + busmaster_parser.lemon candump_parser.lemon ) @@ -160,6 +163,7 @@ CHECKAPI( # LEX files commented out due to use of malloc, free etc. # ${LEX_FILES} ${YACC_FILES} + ${LEMON_FILES} ) # diff --git a/wiretap/busmaster.c b/wiretap/busmaster.c new file mode 100644 index 0000000000..be0e96868a --- /dev/null +++ b/wiretap/busmaster.c @@ -0,0 +1,446 @@ +/* busmaster.c + * + * Wiretap Library + * Copyright (c) 1998 by Gilbert Ramirez <gram@alumni.rice.edu> + * + * Support for Busmaster log file format + * Copyright (c) 2019 by Maksim Salau <maksim.salau@gmail.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" +#include <wtap-int.h> +#include <file_wrappers.h> +#include <epan/exported_pdu.h> +#include <epan/dissectors/packet-socketcan.h> +#include "busmaster.h" +#include "busmaster_priv.h" +#include <inttypes.h> +#include <string.h> +#include <errno.h> + +static void +busmaster_close(wtap *wth); + +static gboolean +busmaster_read(wtap *wth, wtap_rec *rec, Buffer *buf, + int *err, gchar **err_info, + gint64 *data_offset); + +static gboolean +busmaster_seek_read(wtap *wth, gint64 seek_off, + wtap_rec *rec, Buffer *buf, + int *err, gchar **err_info); + +static gboolean +busmaster_gen_packet(wtap_rec *rec, Buffer *buf, + const busmaster_priv_t *priv_entry, const msg_t *msg, + int *err, gchar **err_info) +{ + time_t secs = 0; + guint32 nsecs = 0; + gboolean has_ts = FALSE; + gboolean is_fd = (msg->type == MSG_TYPE_STD_FD) + || (msg->type == MSG_TYPE_EXT_FD); + gboolean is_eff = (msg->type == MSG_TYPE_EXT) + || (msg->type == MSG_TYPE_EXT_RTR) + || (msg->type == MSG_TYPE_EXT_FD); + gboolean is_rtr = (msg->type == MSG_TYPE_STD_RTR) + || (msg->type == MSG_TYPE_EXT_RTR); + gboolean is_err = (msg->type == MSG_TYPE_ERR); + + static const char *const can_proto_name = "can-hostendian"; + static const char *const canfd_proto_name = "canfd"; + + const char *proto_name = is_fd ? canfd_proto_name : can_proto_name; + guint proto_name_length = (guint)strlen(proto_name) + 1; + guint header_length; + guint packet_length; + guint frame_length; + guint8 *buf_data; + + /* Adjust proto name length to be aligned on 4 byte boundary */ + proto_name_length += (proto_name_length % 4) ? (4 - (proto_name_length % 4)) : 0; + + header_length = 4 + proto_name_length + 4; + frame_length = is_fd ? sizeof(canfd_frame_t) : sizeof(can_frame_t); + packet_length = header_length + frame_length; + + ws_buffer_clean(buf); + ws_buffer_assure_space(buf, packet_length); + buf_data = ws_buffer_start_ptr(buf); + + memset(buf_data, 0, packet_length); + + buf_data[1] = EXP_PDU_TAG_PROTO_NAME; + buf_data[3] = proto_name_length; + memcpy(buf_data + 4, proto_name, strlen(proto_name)); + + if (!priv_entry) + { + *err = WTAP_ERR_BAD_FILE; + *err_info = g_strdup("Header is missing"); + return FALSE; + } + + if (is_fd) + { + canfd_frame_t canfd_frame; + + memset(&canfd_frame, 0, sizeof(canfd_frame)); + canfd_frame.can_id = (msg->id & (is_eff ? CAN_EFF_MASK : CAN_SFF_MASK)) | + (is_eff ? CAN_EFF_FLAG : 0) | + (is_err ? CAN_ERR_FLAG : 0); + canfd_frame.flags = 0; + canfd_frame.len = msg->data.length; + + memcpy(canfd_frame.data, + msg->data.data, + MIN(msg->data.length, sizeof(canfd_frame.data))); + + memcpy(buf_data + header_length, + (guint8 *)&canfd_frame, + sizeof(canfd_frame)); + } + else + { + can_frame_t can_frame; + + memset(&can_frame, 0, sizeof(can_frame)); + can_frame.can_id = (msg->id & (is_eff ? CAN_EFF_MASK : CAN_SFF_MASK)) | + (is_rtr ? CAN_RTR_FLAG : 0) | + (is_eff ? CAN_EFF_FLAG : 0) | + (is_err ? CAN_ERR_FLAG : 0); + can_frame.can_dlc = msg->data.length; + + memcpy(can_frame.data, + msg->data.data, + MIN(msg->data.length, sizeof(can_frame.data))); + + memcpy(buf_data + header_length, + (guint8 *)&can_frame, + sizeof(can_frame)); + } + + if (priv_entry->time_mode == TIME_MODE_SYSTEM) + { + struct tm tm; + + tm.tm_year = priv_entry->start_date.year - 1900; + tm.tm_mon = priv_entry->start_date.month - 1; + tm.tm_mday = priv_entry->start_date.day; + tm.tm_hour = msg->timestamp.hours; + tm.tm_min = msg->timestamp.minutes; + tm.tm_sec = msg->timestamp.seconds; + + secs = mktime(&tm); + nsecs = msg->timestamp.micros * 1000u; + has_ts = TRUE; + } + else if (priv_entry->time_mode == TIME_MODE_ABSOLUTE) + { + struct tm tm; + guint32 micros; + + tm.tm_year = priv_entry->start_date.year - 1900; + tm.tm_mon = priv_entry->start_date.month - 1; + tm.tm_mday = priv_entry->start_date.day; + tm.tm_hour = priv_entry->start_time.hours; + tm.tm_min = priv_entry->start_time.minutes; + tm.tm_sec = priv_entry->start_time.seconds; + + secs = mktime(&tm); + + secs += msg->timestamp.hours * 3600; + secs += msg->timestamp.minutes * 60; + secs += msg->timestamp.seconds; + + micros = priv_entry->start_time.micros + msg->timestamp.micros; + if (micros >= 1000000u) + { + micros -= 1000000u; + secs += 1; + } + + nsecs = micros * 1000u; + has_ts = TRUE; + } + + rec->rec_type = REC_TYPE_PACKET; + rec->presence_flags = has_ts ? WTAP_HAS_TS : 0; + rec->ts.secs = secs; + rec->ts.nsecs = nsecs; + + rec->rec_header.packet_header.caplen = packet_length; + rec->rec_header.packet_header.len = packet_length; + + return TRUE; +} + +static log_entry_type_t +busmaster_parse(FILE_T fh, busmaster_state_t *state, int *err, char **err_info) +{ + gboolean ok; + gint64 seek_off; + + busmaster_debug_printf("%s: Running busmaster file decoder\n", G_STRFUNC); + + state->fh = fh; + + do + { + if (file_eof(fh)) + return LOG_ENTRY_EOF; + + seek_off = file_tell(fh); + busmaster_debug_printf("%s: Starting parser at offset %" PRIi64 "\n", + G_STRFUNC, seek_off); + state->file_bytes_read = 0; + ok = run_busmaster_parser(state, err, err_info); + + /* Rewind the file to the offset we have finished parsing */ + busmaster_debug_printf("%s: Rewinding to offset %" PRIi64 "\n", + G_STRFUNC, seek_off + state->file_bytes_read); + if (file_seek(fh, seek_off + state->file_bytes_read, SEEK_SET, err) == -1) + { + g_free(*err_info); + *err = errno; + *err_info = g_strdup(g_strerror(errno)); + return LOG_ENTRY_ERROR; + } + } + while (ok && state->entry_type == LOG_ENTRY_NONE); + + if (!ok) + return LOG_ENTRY_ERROR; + + busmaster_debug_printf("%s: Success\n", G_STRFUNC); + + return state->entry_type; +} + +wtap_open_return_val +busmaster_open(wtap *wth, int *err, char **err_info) +{ + busmaster_state_t state; + log_entry_type_t entry; + + busmaster_debug_printf("%s: Trying to open with busmaster log reader\n", + G_STRFUNC); + + /* Rewind to the beginning */ + if (file_seek(wth->fh, 0, SEEK_SET, err) == -1) + return WTAP_OPEN_ERROR; + + memset(&state, 0, sizeof(state)); + entry = busmaster_parse(wth->fh, &state, err, err_info); + + g_free(*err_info); + *err_info = NULL; + *err = 0; + + if (entry != LOG_ENTRY_HEADER) + return WTAP_OPEN_NOT_MINE; + + /* Rewind to the beginning, so busmaster_read may read from the very beginning */ + if (file_seek(wth->fh, 0, SEEK_SET, err) == -1) + return WTAP_OPEN_ERROR; + + busmaster_debug_printf("%s: That's a busmaster log\n", G_STRFUNC); + + wth->priv = NULL; + wth->subtype_close = busmaster_close; + wth->subtype_read = busmaster_read; + wth->subtype_seek_read = busmaster_seek_read; + wth->file_type_subtype = WTAP_FILE_TYPE_SUBTYPE_UNKNOWN; + wth->file_encap = WTAP_ENCAP_WIRESHARK_UPPER_PDU; + wth->file_tsprec = WTAP_TSPREC_USEC; + + return WTAP_OPEN_MINE; +} + +static void +busmaster_close(wtap *wth) +{ + busmaster_debug_printf("%s\n", G_STRFUNC); + + g_slist_free_full((GSList *)wth->priv, g_free); + wth->priv = NULL; +} + +static busmaster_priv_t * +busmaster_find_priv_entry(void *priv, gint64 offset) +{ + GSList *list; + + for (list = (GSList *)priv; list; list = g_slist_next(list)) + { + busmaster_priv_t *entry = (busmaster_priv_t *)list->data; + + if (((entry->file_end_offset == -1) + && (g_slist_next(list) == NULL)) + || ((offset >= entry->file_start_offset) + && (offset <= entry->file_end_offset))) + { + return entry; + } + } + + return NULL; +} + +static gboolean +busmaster_read(wtap *wth, wtap_rec *rec, Buffer *buf, int *err, gchar **err_info, + gint64 *data_offset) +{ + log_entry_type_t entry; + busmaster_state_t state; + busmaster_priv_t *priv_entry; + gboolean is_msg = FALSE; + gboolean is_ok = TRUE; + + while (!is_msg && is_ok) + { + busmaster_debug_printf("%s: offset = %" PRIi64 "\n", + G_STRFUNC, file_tell(wth->fh)); + + if (file_eof(wth->fh)) + { + busmaster_debug_printf("%s: End of file detected, nothing to do here\n", + G_STRFUNC); + *err = 0; + *err_info = NULL; + return FALSE; + } + + *data_offset = file_tell(wth->fh); + priv_entry = busmaster_find_priv_entry(wth->priv, *data_offset); + + memset(&state, 0, sizeof(state)); + if (priv_entry) + state.header = *priv_entry; + entry = busmaster_parse(wth->fh, &state, err, err_info); + + busmaster_debug_printf("%s: analyzing output\n", G_STRFUNC); + switch (entry) + { + case LOG_ENTRY_EMPTY: + break; + case LOG_ENTRY_FOOTER_AND_HEADER: + case LOG_ENTRY_FOOTER: + priv_entry = (busmaster_priv_t *)g_slist_last((GSList *)wth->priv)->data; + if (!priv_entry) + { + *err = WTAP_ERR_BAD_FILE; + *err_info = g_strdup("Header is missing"); + return FALSE; + } + priv_entry->file_end_offset = *data_offset; + if (entry == LOG_ENTRY_FOOTER) + break; + /* fall-through */ + case LOG_ENTRY_HEADER: + if (state.header.protocol != PROTOCOL_CAN && + state.header.protocol != PROTOCOL_J1939) + { + *err = WTAP_ERR_UNSUPPORTED; + *err_info = g_strdup("Unsupported protocol type"); + return FALSE; + } + + if (wth->priv) + { + /* Check that the previous section has a footer */ + priv_entry = (busmaster_priv_t *)g_slist_last((GSList *)wth->priv)->data; + + if (priv_entry && priv_entry->file_end_offset == -1) + { + *err = WTAP_ERR_BAD_FILE; + *err_info = g_strdup("Footer is missing"); + return FALSE; + } + } + + /* Start a new section */ + priv_entry = g_new(busmaster_priv_t, 1); + + priv_entry[0] = state.header; + priv_entry->file_start_offset = file_tell(wth->fh); + priv_entry->file_end_offset = -1; + + wth->priv = g_slist_append((GSList *)wth->priv, priv_entry); + break; + case LOG_ENTRY_MSG: + is_msg = TRUE; + priv_entry = busmaster_find_priv_entry(wth->priv, *data_offset); + is_ok = busmaster_gen_packet(rec, buf, priv_entry, &state.msg, err, err_info); + break; + case LOG_ENTRY_EOF: + case LOG_ENTRY_ERROR: + case LOG_ENTRY_NONE: + default: + is_ok = FALSE; + break; + } + } + + busmaster_debug_printf("%s: stopped at offset %" PRIi64 " with entry %d\n", + G_STRFUNC, file_tell(wth->fh), entry); + + return is_ok; +} + +static gboolean +busmaster_seek_read(wtap *wth, gint64 seek_off, wtap_rec *rec, + Buffer *buf, int *err, gchar **err_info) +{ + busmaster_priv_t *priv_entry; + busmaster_state_t state; + log_entry_type_t entry; + + busmaster_debug_printf("%s: offset = %" PRIi64 "\n", G_STRFUNC, seek_off); + + priv_entry = busmaster_find_priv_entry(wth->priv, seek_off); + if (!priv_entry) + { + busmaster_debug_printf("%s: analyzing output\n", G_STRFUNC); + *err = WTAP_ERR_BAD_FILE; + *err_info = g_strdup("Malformed header"); + return FALSE; + } + + if (file_seek(wth->random_fh, seek_off, SEEK_SET, err) == -1) + return FALSE; + + memset(&state, 0, sizeof(state)); + state.header = *priv_entry; + entry = busmaster_parse(wth->random_fh, &state, err, err_info); + + busmaster_debug_printf("%s: analyzing output\n", G_STRFUNC); + + if (entry == LOG_ENTRY_ERROR || entry == LOG_ENTRY_NONE) + return FALSE; + + if (entry != LOG_ENTRY_MSG) + { + *err = WTAP_ERR_BAD_FILE; + *err_info = g_strdup("Failed to read a frame"); + return FALSE; + } + + return busmaster_gen_packet(rec, buf, priv_entry, &state.msg, err, err_info); +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/wiretap/busmaster.h b/wiretap/busmaster.h new file mode 100644 index 0000000000..59f5b22645 --- /dev/null +++ b/wiretap/busmaster.h @@ -0,0 +1,20 @@ +/* busmaster.h + * + * Wiretap Library + * Copyright (c) 1998 by Gilbert Ramirez <gram@alumni.rice.edu> + * + * Support for Busmaster log file format + * Copyright (c) 2019 by Maksim Salau <maksim.salau@gmail.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef BUSMASTER_H__ +#define BUSMASTER_H__ + +#include <wiretap/wtap.h> + +wtap_open_return_val +busmaster_open(wtap *wth, int *err, char **err_info); + +#endif /* BUSMASTER_H__ */ diff --git a/wiretap/busmaster_parser.lemon b/wiretap/busmaster_parser.lemon new file mode 100644 index 0000000000..cabe11286f --- /dev/null +++ b/wiretap/busmaster_parser.lemon @@ -0,0 +1,453 @@ +%include { + +/* busmaster_parser.lemon + * + * Wiretap Library + * Copyright (c) 1998 by Gilbert Ramirez <gram@alumni.rice.edu> + * + * Support for Busmaster log file format + * Copyright (c) 2019 by Maksim Salau <maksim.salau@gmail.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" +#include <assert.h> +#include <string.h> +#include <wiretap/file_wrappers.h> +#include "busmaster_priv.h" + +extern void *BusmasterParserAlloc(void *(*mallocProc)(size_t)); +extern void BusmasterParser(void *yyp, int yymajor, token_t yyminor, busmaster_state_t *state); +extern void BusmasterParserFree(void *p, void (*freeProc)(void*)); + +#if defined(BUSMASTER_DEBUG) || defined(BUSMASTER_PARSER_TRACE) +extern void BusmasterParserTrace(FILE *TraceFILE, char *zTracePrompt); +#undef NDEBUG +#endif + +static void merge_msg_data(msg_data_t *dst, const msg_data_t *a, const msg_data_t *b) +{ + dst->length = a->length + b->length; + memcpy(&dst->data[0], &a->data[0], a->length); + memcpy(&dst->data[a->length], &b->data[0], b->length); +} + +DIAG_OFF(unreachable-code) + +} + +%name BusmasterParser + +%token_prefix TOKEN_ + +%token_type { token_t } + +%token_destructor +{ + (void)state; + (void)yypParser; + (void)yypminor; +} + +%extra_argument { busmaster_state_t* state } + +%syntax_error +{ + (void)yypParser; + (void)yyminor; + +#ifdef BUSMASTER_DEBUG + const int n = sizeof(yyTokenName) / sizeof(yyTokenName[0]); + busmaster_debug_printf("%s: got token: %s\n", G_STRFUNC, yyTokenName[yymajor]); + for (int i = 0; i < n; ++i) { + int a = yy_find_shift_action((YYCODETYPE)i, yypParser->yytos->stateno); + if (a < YYNSTATE + YYNRULE) { + busmaster_debug_printf("%s: possible token: %s\n", G_STRFUNC, yyTokenName[i]); + } + } +#endif + + g_free(state->parse_error); + state->entry_type = LOG_ENTRY_ERROR; + state->parse_error = g_strdup_printf("Syntax Error"); + busmaster_debug_printf("%s: Syntax Error\n", G_STRFUNC); +} + +%parse_failure +{ + g_free(state->parse_error); + state->entry_type = LOG_ENTRY_ERROR; + state->parse_error = g_strdup("Parse Error"); + busmaster_debug_printf("%s: Parse Error\n", G_STRFUNC); +} + +%stack_overflow +{ + g_free(state->parse_error); + state->entry_type = LOG_ENTRY_ERROR; + state->parse_error = g_strdup("Parser stack overflow"); + busmaster_debug_printf("%s: Parser stack overflow\n", G_STRFUNC); +} + +%type msg_time { msg_time_t } +%type msg_type { msg_type_t } +%type err_msg_type { msg_type_t } +%type msg_length { guint } +%type msg_id { guint32 } + +%type ref_date { msg_date_t } +%type ref_time { msg_time_t } + +%type start_time { msg_date_time_t } + +%type byte { guint8 } +%type data { msg_data_t } +%type data0 { msg_data_t } +%type data1 { msg_data_t } +%type data2 { msg_data_t } +%type data3 { msg_data_t } +%type data4 { msg_data_t } +%type data5 { msg_data_t } +%type data6 { msg_data_t } +%type data7 { msg_data_t } +%type data8 { msg_data_t } +%type data12 { msg_data_t } +%type data16 { msg_data_t } +%type data20 { msg_data_t } +%type data24 { msg_data_t } +%type data32 { msg_data_t } +%type data48 { msg_data_t } +%type data64 { msg_data_t } + +%nonassoc INVALID_CHAR . +%nonassoc INVALID_NUMBER . + +%start_symbol entry + +entry ::= empty_line . +entry ::= footer_and_header . +entry ::= header . +entry ::= footer . +entry ::= msg . +entry ::= err_msg . +entry ::= j1939_msg . + +empty_line ::= . +{ + busmaster_debug_printf("%s: EMPTY\n", G_STRFUNC); + state->entry_type = LOG_ENTRY_EMPTY; +} + +footer_and_header ::= footer ENDL header . +{ + busmaster_debug_printf("%s: FOOTER AND HEADER\n", G_STRFUNC); + state->entry_type = LOG_ENTRY_FOOTER_AND_HEADER; +} + +header ::= version ENDL maybe_lines + PROTOCOL_TYPE(P) ENDL maybe_lines + START_SESSION ENDL maybe_lines + start_time(S) ENDL maybe_lines + DATA_MODE(D) ENDL maybe_lines + TIME_MODE(T) ENDL anything . +{ + busmaster_debug_printf("%s: HEADER\n", G_STRFUNC); + + state->entry_type = LOG_ENTRY_HEADER; + state->header.start_date = S.date; + state->header.start_time = S.time; + state->header.protocol = (protocol_t)P.v0; + state->header.data_mode = (data_mode_t)D.v0; + state->header.time_mode = (time_mode_t)T.v0; +} + +version ::= HEADER_VER maybe_chars . + +maybe_chars ::= . +maybe_chars ::= maybe_chars HEADER_CHAR . + +maybe_lines ::= . +maybe_lines ::= maybe_lines maybe_chars ENDL . + +anything ::= . +anything ::= anything HEADER_CHAR . +anything ::= anything ENDL . + +start_time(R) ::= START_TIME ref_date(D) ref_time(T) . +{ + R.date = D; + R.time = T; +} + +footer ::= end_time ENDL STOP_SESSION . +{ + busmaster_debug_printf("%s: FOOTER\n", G_STRFUNC); + state->entry_type = LOG_ENTRY_FOOTER; +} + +end_time ::= END_TIME ref_date ref_time . + +/* <Time><Tx/Rx><Channel><CAN ID><Type><DLC><DataBytes> */ +msg ::= msg_time(msg_time) MSG_DIR INT msg_id(msg_id) msg_type(msg_type) msg_length(msg_length) data(msg_data) . +{ + msg_t msg; + + /* DLC is always in DEC mode, thus we need to fix the value + * if it was read initially as HEX. */ + if (state->header.data_mode == DATA_MODE_HEX) + { + msg_length = (msg_length / 16) * 10 + (msg_length % 16); + } + + /* Fix data in RTR frames. Data may not be present, + * but length field is set. */ + if (msg_type == MSG_TYPE_STD_RTR || + msg_type == MSG_TYPE_EXT_RTR) + { + memset(&msg_data, 0, sizeof(msg_data)); + msg_data.length = msg_length; + } + + msg.timestamp = msg_time; + msg.id = msg_id; + msg.type = msg_type; + msg.data = msg_data; + + busmaster_debug_printf("%s: MSG\n", G_STRFUNC); + + state->msg = msg; + state->entry_type = LOG_ENTRY_MSG; +} + +/* <Time><Tx/Rx><Channel><CAN ID><Type><Text> */ +err_msg ::= msg_time(msg_time) MSG_DIR INT INT err_msg_type(msg_type) . +{ + msg_t msg; + + msg.timestamp = msg_time; + msg.id = 0; + msg.type = msg_type; + msg.data.length = CAN_MAX_DLEN; + + memset(msg.data.data, 0, sizeof(msg.data.data)); + + busmaster_debug_printf("%s: ERR MSG\n", G_STRFUNC); + + state->msg = msg; + state->entry_type = LOG_ENTRY_MSG; +} + +/* <Time><Channel><CAN ID><PGN><Type><Source Node><Destination Node><Priority><Tx/Rx><DLC><DataBytes> */ +j1939_msg ::= msg_time(msg_time) INT msg_id(msg_id) INT J1939_MSG_TYPE INT INT INT MSG_DIR msg_length data(msg_data) . +{ + msg_t msg; + + msg.timestamp = msg_time; + msg.id = msg_id; + msg.type = MSG_TYPE_EXT; + msg.data = msg_data; + + busmaster_debug_printf("%s: J1939 MSG\n", G_STRFUNC); + + state->msg = msg; + state->entry_type = LOG_ENTRY_MSG; +} + +ref_date(R) ::= INT(D) COLON INT(M) COLON INT(Y) . +{ + R.year = (guint)Y.v0; + R.month = (guint)M.v0; + R.day = (guint)D.v0; +} + +ref_time(R) ::= INT(H) COLON INT(M) COLON INT(S) COLON INT(U) . +{ + R.hours = (guint)H.v0; + R.minutes = (guint)M.v0; + R.seconds = (guint)S.v0; + R.micros = (guint)U.v0 * 1000; +} + +msg_time(R) ::= MSG_TIME(M) . +{ + R.hours = (guint)M.v0; + R.minutes = (guint)M.v1; + R.seconds = (guint)M.v2; + R.micros = (guint)M.v3 * 100; +} + +msg_id(R) ::= INT(V) . +{ + R = (guint)V.v0; +} + +msg_length(R) ::= INT(V) . +{ + R = (guint)V.v0; +} + +msg_type(R) ::= MSG_TYPE(V) . +{ + R = (msg_type_t)V.v0; +} + +err_msg_type(R) ::= ERR_MSG_TYPE(V) . +{ + R = (msg_type_t)V.v0; +} + +data(R) ::= data0(A) . { R = A; } +data(R) ::= data1(A) . { R = A; } +data(R) ::= data2(A) . { R = A; } +data(R) ::= data3(A) . { R = A; } +data(R) ::= data4(A) . { R = A; } +data(R) ::= data5(A) . { R = A; } +data(R) ::= data6(A) . { R = A; } +data(R) ::= data7(A) . { R = A; } +data(R) ::= data8(A) . { R = A; } +data(R) ::= data12(A) . { R = A; } +data(R) ::= data16(A) . { R = A; } +data(R) ::= data20(A) . { R = A; } +data(R) ::= data24(A) . { R = A; } +data(R) ::= data32(A) . { R = A; } +data(R) ::= data48(A) . { R = A; } +data(R) ::= data64(A) . { R = A; } + +byte(R) ::= INT(A) . +{ + R = (guint8)A.v0; +} + +data0(R) ::= . +{ + R.length = 0; +} + +data1(R) ::= byte(A) . +{ + R.length = 1; + R.data[0] = A; +} + +data2(R) ::= byte(A) byte(B) . +{ + R.length = 2; + R.data[0] = A; + R.data[1] = B; +} + +data3(R) ::= byte(A) byte(B) byte(C) . +{ + R.length = 3; + R.data[0] = A; + R.data[1] = B; + R.data[2] = C; +} + +data4(R) ::= byte(A) byte(B) byte(C) byte(D) . +{ + R.length = 4; + R.data[0] = A; + R.data[1] = B; + R.data[2] = C; + R.data[3] = D; +} + +data5(R) ::= data4(A) data1(B) . { merge_msg_data(&R, &A, &B); } +data6(R) ::= data4(A) data2(B) . { merge_msg_data(&R, &A, &B); } +data7(R) ::= data4(A) data3(B) . { merge_msg_data(&R, &A, &B); } +data8(R) ::= data4(A) data4(B) . { merge_msg_data(&R, &A, &B); } +data12(R) ::= data8(A) data4(B) . { merge_msg_data(&R, &A, &B); } +data16(R) ::= data8(A) data8(B) . { merge_msg_data(&R, &A, &B); } +data20(R) ::= data16(A) data4(B) . { merge_msg_data(&R, &A, &B); } +data24(R) ::= data16(A) data8(B) . { merge_msg_data(&R, &A, &B); } +data32(R) ::= data16(A) data16(B) . { merge_msg_data(&R, &A, &B); } +data48(R) ::= data32(A) data16(B) . { merge_msg_data(&R, &A, &B); } +data64(R) ::= data32(A) data32(B) . { merge_msg_data(&R, &A, &B); } + +%code { + +DIAG_ON(unreachable-code) + +#include "busmaster_scanner_lex.h" +#include "busmaster_parser.h" + +gboolean +run_busmaster_parser(busmaster_state_t *state, + int *err, gchar **err_info) +{ + int lex_code; + yyscan_t scanner; + void *parser; + + state->entry_type = LOG_ENTRY_NONE; + state->parse_error = NULL; + state->err = 0; + state->err_info = NULL; + + if (busmaster_lex_init_extra(state, &scanner) != 0) + { + *err = errno; + *err_info = g_strdup(g_strerror(errno)); + return FALSE; + } + + parser = BusmasterParserAlloc(g_malloc); + +#ifdef BUSMASTER_PARSER_TRACE + BusmasterParserTrace(stdout, "BusmasterParser >> "); +#endif + + busmaster_debug_printf("%s: Starting parsing of the line\n", G_STRFUNC); + + do + { + lex_code = busmaster_lex(scanner); + +#ifdef BUSMASTER_DEBUG + if (lex_code) + busmaster_debug_printf("%s: Feeding %s '%s'\n", + G_STRFUNC, yyTokenName[lex_code], + busmaster_get_text(scanner)); + else + busmaster_debug_printf("%s: Feeding %s\n", + G_STRFUNC, yyTokenName[lex_code]); +#endif + + BusmasterParser(parser, lex_code, state->token, state); + + if (state->err || state->err_info || state->parse_error) + break; + } + while (lex_code); + + busmaster_debug_printf("%s: Done (%d)\n", G_STRFUNC, lex_code); + + BusmasterParserFree(parser, g_free); + busmaster_lex_destroy(scanner); + + if (state->err || state->err_info || state->parse_error) + { + if (state->err_info) + { + *err_info = state->err_info; + g_free(state->parse_error); + } + else + { + *err_info = state->parse_error; + } + + if (state->err) + *err = state->err; + else + *err = WTAP_ERR_BAD_FILE; + + return FALSE; + } + + return TRUE; +} + +} diff --git a/wiretap/busmaster_priv.h b/wiretap/busmaster_priv.h new file mode 100644 index 0000000000..36ce70e994 --- /dev/null +++ b/wiretap/busmaster_priv.h @@ -0,0 +1,136 @@ +/* busmaster_priv.h + * + * Wiretap Library + * Copyright (c) 1998 by Gilbert Ramirez <gram@alumni.rice.edu> + * + * Support for Busmaster log file format + * Copyright (c) 2019 by Maksim Salau <maksim.salau@gmail.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef BUSMASTER_PRIV_H__ +#define BUSMASTER_PRIV_H__ + +#include <gmodule.h> +#include <wiretap/wtap.h> +#include <wiretap/socketcan.h> +#include <wsutil/ws_printf.h> + +//#define BUSMASTER_DEBUG +//#define BUSMASTER_PARSER_TRACE + +typedef enum { + LOG_ENTRY_ERROR = -1, + LOG_ENTRY_NONE = 0, + LOG_ENTRY_EMPTY, + LOG_ENTRY_HEADER, + LOG_ENTRY_FOOTER, + LOG_ENTRY_FOOTER_AND_HEADER, + LOG_ENTRY_MSG, + LOG_ENTRY_EOF, +} log_entry_type_t; + +typedef enum { + PROTOCOL_UNKNOWN = 0, + PROTOCOL_CAN, + PROTOCOL_LIN, + PROTOCOL_J1939, +} protocol_t; + +typedef enum { + DATA_MODE_UNKNOWN = 0, + DATA_MODE_HEX, + DATA_MODE_DEC, +} data_mode_t; + +typedef enum { + TIME_MODE_UNKNOWN = 0, + TIME_MODE_ABSOLUTE, + TIME_MODE_SYSTEM, + TIME_MODE_RELATIVE, +} time_mode_t; + +typedef enum { + MSG_TYPE_STD, + MSG_TYPE_EXT, + MSG_TYPE_STD_RTR, + MSG_TYPE_EXT_RTR, + MSG_TYPE_STD_FD, + MSG_TYPE_EXT_FD, + MSG_TYPE_ERR, +} msg_type_t; + +typedef struct { + guint year; + guint month; + guint day; +} msg_date_t; + +typedef struct { + guint hours; + guint minutes; + guint seconds; + guint micros; +} msg_time_t; + +typedef struct { + msg_date_t date; + msg_time_t time; +} msg_date_time_t; + +typedef struct { + guint length; + guint8 data[CANFD_MAX_DLEN]; +} msg_data_t; + +typedef struct { + msg_time_t timestamp; + msg_type_t type; + guint32 id; + msg_data_t data; +} msg_t; + +typedef struct { + gint64 v0; + gint64 v1; + gint64 v2; + gint64 v3; +} token_t; + +typedef struct { + gint64 file_start_offset; + gint64 file_end_offset; + protocol_t protocol; + data_mode_t data_mode; + time_mode_t time_mode; + msg_date_t start_date; + msg_time_t start_time; +} busmaster_priv_t; + +typedef struct { + FILE_T fh; + gint64 file_bytes_read; + + gchar *parse_error; + int err; + gchar *err_info; + + token_t token; + + log_entry_type_t entry_type; + busmaster_priv_t header; + msg_t msg; +} busmaster_state_t; + +gboolean +run_busmaster_parser(busmaster_state_t *state, + int *err, gchar **err_info); + +#ifdef BUSMASTER_DEBUG +#define busmaster_debug_printf(...) ws_debug_printf(__VA_ARGS__) +#else +#define busmaster_debug_printf(...) (void)0 +#endif + +#endif /* BUSMASTER_PRIV_H__ */ diff --git a/wiretap/busmaster_scanner.l b/wiretap/busmaster_scanner.l new file mode 100644 index 0000000000..4a352b961c --- /dev/null +++ b/wiretap/busmaster_scanner.l @@ -0,0 +1,198 @@ +/* busmaster_scanner.l + * + * Wiretap Library + * Copyright (c) 1998 by Gilbert Ramirez <gram@alumni.rice.edu> + * + * Support for Busmaster log file format + * Copyright (c) 2019 by Maksim Salau <maksim.salau@gmail.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +%top { +/* Include this before everything else, for various large-file definitions */ +#include "config.h" +} + +%option noyywrap +%option noinput +%option nounput +%option batch +%option never-interactive +%option nodefault +%option prefix="busmaster_" +%option reentrant +%option extra-type="busmaster_state_t *" + +%option noyy_scan_buffer +%option noyy_scan_bytes +%option noyy_scan_string + +/* + * We have to override the memory allocators so that we don't get + * "unused argument" warnings from the yyscanner argument (which + * we don't use, as we have a global memory allocator). + * + * We provide, as macros, our own versions of the routines generated by Flex, + * which just call malloc()/realloc()/free() (as the Flex versions do), + * discarding the extra argument. + */ +%option noyyalloc +%option noyyrealloc +%option noyyfree + +%{ + +#include <ws_diag_control.h> +#include <wiretap/file_wrappers.h> +#include "busmaster_parser.h" +#include "busmaster_priv.h" + +#ifndef HAVE_UNISTD_H +#define YY_NO_UNISTD_H +#endif + +static int busmaster_yyinput(void *buf, unsigned int length, busmaster_state_t *state) +{ + int ret = file_read(buf, length, state->fh); + + if (ret < 0) + { + state->err = file_error(state->fh, &state->err_info); + return YY_NULL; + } + + return ret; +} + +#define YY_INPUT(buf, result, max_size) \ + do { (result) = busmaster_yyinput((buf), (max_size), yyextra); } while (0) + +/* Count bytes read. This is required in order to rewind the file + * to the beginning of the next packet, since flex reads more bytes + * before executing the action that does yyterminate(). */ +#define YY_USER_ACTION do { yyextra->file_bytes_read += yyleng; } while (0); + +/* + * Sleazy hack to suppress compiler warnings in yy_fatal_error(). + */ +#define YY_EXIT_FAILURE ((void)yyscanner, 2) + +/* + * Macros for the allocators, to discard the extra argument. + */ +#define busmaster_alloc(size, yyscanner) (void *)malloc(size) +#define busmaster_realloc(ptr, size, yyscanner) (void *)realloc((char *)(ptr), (size)) +#define busmaster_free(ptr, yyscanner) free((char *)(ptr)) + +DIAG_OFF_FLEX + +%} + +SPC [ \t]+ +ENDL [\r\n][ \t\r\n]* +INT [0-9]+ +NUM (0x)?[0-9A-Fa-f]+ + +%x HEADER TIME +%x HEADER_CHANNELS HEADER_DB_FILES + +%% + +<*>{SPC} ; +<INITIAL>{ENDL} { yyterminate(); }; +<HEADER,TIME>{ENDL} { YY_FATAL_ERROR("Unterminated header statement"); } + +"***" { BEGIN(HEADER); } +<HEADER,TIME>"***"{ENDL}"***" { BEGIN(HEADER); return TOKEN_ENDL; } +<HEADER>"***"{ENDL} { BEGIN(INITIAL); yyterminate(); } +<HEADER>"BUSMASTER" { return TOKEN_HEADER_VER; } +<HEADER>"PROTOCOL CAN" { yyextra->token.v0 = PROTOCOL_CAN; return TOKEN_PROTOCOL_TYPE; } +<HEADER>"PROTOCOL J1939" { yyextra->token.v0 = PROTOCOL_J1939; return TOKEN_PROTOCOL_TYPE; } +<HEADER>"PROTOCOL LIN" { yyextra->token.v0 = PROTOCOL_LIN; return TOKEN_PROTOCOL_TYPE; } +<HEADER>"START DATE AND TIME" { BEGIN(TIME); return TOKEN_START_TIME; } +<HEADER>"END DATE AND TIME" { BEGIN(TIME); return TOKEN_END_TIME; } +<HEADER>"DEC" { yyextra->token.v0 = DATA_MODE_DEC; return TOKEN_DATA_MODE; } +<HEADER>"HEX" { yyextra->token.v0 = DATA_MODE_HEX; return TOKEN_DATA_MODE; } +<HEADER>"ABSOLUTE MODE" { yyextra->token.v0 = TIME_MODE_ABSOLUTE; return TOKEN_TIME_MODE; } +<HEADER>"SYSTEM MODE" { yyextra->token.v0 = TIME_MODE_SYSTEM; return TOKEN_TIME_MODE; } +<HEADER>"RELATIVE MODE" { yyextra->token.v0 = TIME_MODE_RELATIVE; return TOKEN_TIME_MODE; } +<HEADER>"[START LOGGING SESSION]" { return TOKEN_START_SESSION; } +<HEADER>"[STOP LOGGING SESSION]" { return TOKEN_STOP_SESSION; } +<HEADER>"START CHANNEL BAUD RATE***" { BEGIN(HEADER_CHANNELS); } +<HEADER>"START DATABASE FILES (DBF/DBC)***" { BEGIN(HEADER_DB_FILES); } +<HEADER>. { return TOKEN_HEADER_CHAR; } + +<HEADER_CHANNELS>"***END CHANNEL BAUD RATE***"{ENDL}"***" { BEGIN(HEADER); } +<HEADER_CHANNELS>.+ ; +<HEADER_CHANNELS>{ENDL} ; + +<HEADER_DB_FILES>"***END DATABASE FILES (DBF/DBC)***"{ENDL}"***" { BEGIN(HEADER); } +<HEADER_DB_FILES>"***END OF DATABASE FILES (DBF/DBC)***"{ENDL}"***" { BEGIN(HEADER); } +<HEADER_DB_FILES>.+ ; +<HEADER_DB_FILES>{ENDL} ; + +<TIME>{INT} { yyextra->token.v0 = strtoul(yytext, NULL, 10); return TOKEN_INT; } +<TIME>: { return TOKEN_COLON; } +<TIME>. { return TOKEN_INVALID_CHAR; } + +<INITIAL>{INT}:{INT}:{INT}:{INT} { + char *endp; + char *strp; + + yyextra->token.v0 = strtoul(yytext, &endp, 10); + if (*endp != ':' || endp == yytext) + return TOKEN_INVALID_NUMBER; + + strp = endp + 1; + yyextra->token.v1 = strtoul(strp, &endp, 10); + if (*endp != ':' || endp == strp) + return TOKEN_INVALID_NUMBER; + + strp = endp + 1; + yyextra->token.v2 = strtoul(strp, &endp, 10); + if (*endp != ':' || endp == strp) + return TOKEN_INVALID_NUMBER; + + strp = endp + 1; + yyextra->token.v3 = strtoul(strp, &endp, 10); + if (*endp != '\0' || endp == strp) + return TOKEN_INVALID_NUMBER; + + return TOKEN_MSG_TIME; + } + +<INITIAL>{NUM} { + char *endp; + + if (yyextra->header.data_mode == DATA_MODE_HEX) + yyextra->token.v0 = strtoul(yytext, &endp, 16); + else if (yyextra->header.data_mode == DATA_MODE_DEC) + yyextra->token.v0 = strtoul(yytext, &endp, 10); + else + return TOKEN_INVALID_NUMBER; + + if (*endp != '\0' || endp == yytext) + return TOKEN_INVALID_NUMBER; + + return TOKEN_INT; + } + +<INITIAL>[RT]x { return TOKEN_MSG_DIR; } +<INITIAL>s { yyextra->token.v0 = MSG_TYPE_STD; return TOKEN_MSG_TYPE; } +<INITIAL>sr { yyextra->token.v0 = MSG_TYPE_STD_RTR; return TOKEN_MSG_TYPE; } +<INITIAL>x { yyextra->token.v0 = MSG_TYPE_EXT; return TOKEN_MSG_TYPE; } +<INITIAL>xr { yyextra->token.v0 = MSG_TYPE_EXT_RTR; return TOKEN_MSG_TYPE; } +<INITIAL>s-fd { yyextra->token.v0 = MSG_TYPE_STD_FD; return TOKEN_MSG_TYPE; } +<INITIAL>x-fd { yyextra->token.v0 = MSG_TYPE_EXT_FD; return TOKEN_MSG_TYPE; } +<INITIAL>ERR.* { yyextra->token.v0 = MSG_TYPE_ERR; return TOKEN_ERR_MSG_TYPE; } + +<INITIAL>("NONE"|"CMD"|"RQST"|"DATA"|"BROADCAST"|"ACK"|"GRP_FUNC"|"ACL"|"RQST_ACL"|"CA"|"BAM"|"RTS"|"CTS"|"EOM"|"CON_ABORT"|"TPDT") { + return TOKEN_J1939_MSG_TYPE; +} + +<INITIAL>. { return TOKEN_INVALID_CHAR; } + +%% + +DIAG_ON_FLEX diff --git a/wiretap/candump_priv.h b/wiretap/candump_priv.h index 5d3ad7b156..a505939513 100644 --- a/wiretap/candump_priv.h +++ b/wiretap/candump_priv.h @@ -14,31 +14,11 @@ #include <gmodule.h> #include <wiretap/wtap.h> +#include <wiretap/socketcan.h> #include <epan/dissectors/packet-socketcan.h> //#define CANDUMP_DEBUG -#define CAN_MAX_DLEN 8 -#define CANFD_MAX_DLEN 64 - -typedef struct can_frame { - guint32 can_id; /* 32 bit CAN_ID + EFF/RTR/ERR flags */ - guint8 can_dlc; /* frame payload length in byte (0 .. CAN_MAX_DLEN) */ - guint8 __pad; /* padding */ - guint8 __res0; /* reserved / padding */ - guint8 __res1; /* reserved / padding */ - guint8 data[CAN_MAX_DLEN]; -} can_frame_t; - -typedef struct canfd_frame { - guint32 can_id; /* 32 bit CAN_ID + EFF/RTR/ERR flags */ - guint8 len; /* frame payload length in byte (0 .. CANFD_MAX_DLEN) */ - guint8 flags; /* additional flags for CAN FD */ - guint8 __res0; /* reserved / padding */ - guint8 __res1; /* reserved / padding */ - guint8 data[CANFD_MAX_DLEN]; -} canfd_frame_t; - typedef struct { guint8 length; guint8 data[CANFD_MAX_DLEN]; diff --git a/wiretap/file_access.c b/wiretap/file_access.c index 466dbd58fc..3c5e834e0c 100644 --- a/wiretap/file_access.c +++ b/wiretap/file_access.c @@ -78,6 +78,7 @@ #include "systemd_journal.h" #include "log3gpp.h" #include "candump.h" +#include "busmaster.h" /* @@ -418,6 +419,7 @@ static const struct open_info open_info_base[] = { { "Android Logcat Binary format", OPEN_INFO_HEURISTIC, logcat_open, "logcat", NULL, NULL }, { "Android Logcat Text formats", OPEN_INFO_HEURISTIC, logcat_text_open, "txt", NULL, NULL }, { "Candump log", OPEN_INFO_HEURISTIC, candump_open, NULL, NULL, NULL }, + { "Busmaster log", OPEN_INFO_HEURISTIC, busmaster_open, NULL, NULL, NULL }, /* ASCII trace files from Telnet sessions. */ { "Lucent/Ascend access server trace", OPEN_INFO_HEURISTIC, ascend_open, "txt", NULL, NULL }, { "Toshiba Compact ISDN Router snoop", OPEN_INFO_HEURISTIC, toshiba_open, "txt", NULL, NULL }, diff --git a/wiretap/socketcan.h b/wiretap/socketcan.h new file mode 100644 index 0000000000..a287681fa7 --- /dev/null +++ b/wiretap/socketcan.h @@ -0,0 +1,38 @@ +/* socketcan.h + * + * Wiretap Library + * Copyright (c) 1998 by Gilbert Ramirez <gram@alumni.rice.edu> + * + * Support for Busmaster log file format + * Copyright (c) 2019 by Maksim Salau <maksim.salau@gmail.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef SOCKETCAN_H__ +#define SOCKETCAN_H__ + +#include <gmodule.h> + +#define CAN_MAX_DLEN 8 +#define CANFD_MAX_DLEN 64 + +typedef struct can_frame { + guint32 can_id; /* 32 bit CAN_ID + EFF/RTR/ERR flags */ + guint8 can_dlc; /* frame payload length in byte (0 .. CAN_MAX_DLEN) */ + guint8 __pad; /* padding */ + guint8 __res0; /* reserved / padding */ + guint8 __res1; /* reserved / padding */ + guint8 data[CAN_MAX_DLEN]; +} can_frame_t; + +typedef struct canfd_frame { + guint32 can_id; /* 32 bit CAN_ID + EFF flag */ + guint8 len; /* frame payload length in byte */ + guint8 flags; /* additional flags for CAN FD */ + guint8 __res0; /* reserved / padding */ + guint8 __res1; /* reserved / padding */ + guint8 data[CANFD_MAX_DLEN]; +} canfd_frame_t; + +#endif /* SOCKETCAN_H__ */ |