wiretap: Add support for Busmaster log file format

Only CAN protocol is supported. Extra information available in J1939
entries is ignored since the J1939 wireshark dissector works with
raw CAN frames and makes no use of this extra information.
The log format may also encapsulate LIN messages which are not
supported by wireshark and thus are ignored.

The only limitation is that relative timestamp format is not
supported. If a file defines relative format of timestamps, packets
are extracted, but timestamps are omitted, since random access deems
impossible without reparsing the whole file up to the packet of
interest. In order to support relative timestamps we need to parse
the whole file at once on open and either dump into a temporary
PCAP file or keep messages in a private list and provide access
to them on read()/seek_read().

The change also creates a separate header for CAN frame structure
definitions which are used by several file readers (candump and
busmaster for now).

Bug: 15939
Change-Id: I87c5555e4e5e1b142b9984b24544b2591d494fbc
Reviewed-on: https://code.wireshark.org/review/34083
Petri-Dish: Anders Broman <a.broman58@gmail.com>
Tested-by: Petri Dish Buildbot
Reviewed-by: Anders Broman <a.broman58@gmail.com>
This commit is contained in:
Maksim Salau 2019-06-27 21:20:02 +03:00 committed by Anders Broman
parent 19488f334d
commit 9011ad1030
9 changed files with 1298 additions and 21 deletions

View File

@ -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}
)
#

446
wiretap/busmaster.c Normal file
View File

@ -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:
*/

20
wiretap/busmaster.h Normal file
View File

@ -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__ */

View File

@ -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;
}
}

136
wiretap/busmaster_priv.h Normal file
View File

@ -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__ */

198
wiretap/busmaster_scanner.l Normal file
View File

@ -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

View File

@ -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];

View File

@ -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 },

38
wiretap/socketcan.h Normal file
View File

@ -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__ */