Use socketcan.[c|h] for shared (Socket)CAN functionality where the wiretaps create records of the WTAP_ENCAP_SOCKETCAN encapsulation type. Adjust existing "homegrown" structures to use as much of the "shared" data structures from socketcan.h so that all can use the single function wtap_socketcan_gen_packet() to create records.
873 lines
30 KiB
C
873 lines
30 KiB
C
/* peak_trc.c
|
|
*
|
|
* Wiretap Library
|
|
* Copyright (c) 1998 by Gilbert Ramirez <gram@alumni.rice.edu>
|
|
*
|
|
* Support for TRC log file format generated by several CAN tools from PEAK Gmbh
|
|
* Copyright (c) 2024 by Miklos Marton <martonmiklosqdev@gmail.com>
|
|
*
|
|
* File format description:
|
|
* https://www.peak-system.com/produktcd/Pdf/English/PEAK_CAN_TRC_File_Format.pdf
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
|
|
#include <config.h>
|
|
#include <file_wrappers.h>
|
|
#include <epan/dissectors/packet-socketcan.h>
|
|
#include <wctype.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include <time.h>
|
|
#include "peak-trc.h"
|
|
#include "socketcan.h"
|
|
|
|
//#define PEAK_TRC_DEBUG
|
|
#ifdef PEAK_TRC_DEBUG
|
|
#include <stdio.h>
|
|
#define peak_trc_debug_printf(...) printf(__VA_ARGS__)
|
|
#else
|
|
#define peak_trc_debug_printf(...) (void)0
|
|
#endif
|
|
|
|
#define PEAK_TRC_MAX_LINE_SIZE 4096 // J1939 logs could contain long lines
|
|
|
|
typedef enum {
|
|
FileVersion,
|
|
StartTime,
|
|
ColumnHeader,
|
|
Comment,
|
|
Data
|
|
} peak_trc_parse_state_t;
|
|
|
|
typedef enum {
|
|
Col_BusNumber, // 0
|
|
Col_BusNumber_v1, // 1
|
|
Col_Direction, // 2
|
|
Col_CanId, // 3
|
|
Col_DLC, // 4
|
|
Col_MessageNumber, // 5
|
|
Col_TimeOffset_v1_0, // 6
|
|
Col_TimeOffset, // 7
|
|
Col_Reserved, // 8
|
|
Col_MessageType, // 9
|
|
Col_ErrorOrRtr_v1p0, // 10
|
|
// keep it to the last one to get the
|
|
Col_Data_v1_0, // 11
|
|
Col_Data, // 12
|
|
Col_Invalid // 13
|
|
} peak_trc_column_type_t;
|
|
|
|
typedef struct {
|
|
peak_trc_column_type_t type;
|
|
char code;
|
|
char* regex;
|
|
} peak_trc_column_map_t;
|
|
|
|
typedef struct {
|
|
guint32 flags;
|
|
wtap_can_msg_t msg;
|
|
uint8_t bus_id;
|
|
} peak_trc_msg_t;
|
|
|
|
#define PEAK_TRC_MSG_FLAG_BUS_ID 1
|
|
|
|
typedef struct {
|
|
time_t trace_start;
|
|
time_t trace_start_nsec;
|
|
|
|
uint8_t column_positions[Col_Invalid];
|
|
|
|
uint8_t file_version_major, file_version_minor;
|
|
GRegex* data_matcher;
|
|
} peak_trc_state_t;
|
|
|
|
static int peak_trc_file_type_subtype = -1;
|
|
|
|
static peak_trc_column_map_t colmap[] = {
|
|
{Col_BusNumber, 'B', "\\s*(([0-9]*)|-)"},
|
|
/* This isn't a real column type, so give it a "type" that shouldn't be used */
|
|
{Col_BusNumber_v1, 1, "\\s*([0-9]*)"},
|
|
{Col_Direction, 'd', "\\s*(Rx|Tx|Warng|Error)"},
|
|
/* This isn't a real column type, so give it a "type" that shouldn't be used */
|
|
{Col_Data_v1_0, 2, "\\s?(((--|[0-9A-F]{2})\\s?)*)"},
|
|
{Col_Data, 'D', "\\s?((((--|[0-9A-F]{2})\\s?))*)"},
|
|
{Col_CanId, 'I', "\\s*([0-9A-F]*)"},
|
|
{Col_DLC, 'l', "\\s*([0-9]*)"},
|
|
{Col_DLC, 'L', "\\s*([0-9]*)"},
|
|
{Col_MessageNumber, 'N', "\\s*([0-9]*)\\)"},
|
|
{Col_TimeOffset, 'O', "\\s*([0-9]*\\.[0-9]*)"},
|
|
/* This isn't a real column type, so give it a "type" that shouldn't be used */
|
|
{Col_TimeOffset_v1_0, 3, "\\s*([0-9]*)"},
|
|
{Col_Reserved, 'R', "\\s*(-)"},
|
|
{Col_MessageType, 'T', "\\s*([A-Z]{2})"},
|
|
/* This isn't a real column, so give it a "type" that shouldn't be used */
|
|
{Col_ErrorOrRtr_v1p0, 4, "\\s*(ERROR|RTR)?"},
|
|
{Col_Invalid, 0, ""},
|
|
};
|
|
|
|
void register_peak_trc(void);
|
|
|
|
static bool
|
|
peak_trc_write_packet(wtap* wth, wtap_rec *rec, const peak_trc_msg_t *peak_msg,
|
|
int *err, gchar **err_info)
|
|
{
|
|
if (!wtap_socketcan_gen_packet(wth, rec, &peak_msg->msg, "peak", err, err_info))
|
|
return false;
|
|
|
|
if (peak_msg->flags & PEAK_TRC_MSG_FLAG_BUS_ID)
|
|
{
|
|
rec->presence_flags |= WTAP_HAS_INTERFACE_ID;
|
|
rec->rec_header.packet_header.interface_id = peak_msg->bus_id;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static wtap_open_return_val
|
|
peak_trc_parse(wtap* wth, gint64* offset, int* err, char** err_info)
|
|
{
|
|
gint64 seek_off = 0;
|
|
bool found_timestamp = false;
|
|
char line_buffer[PEAK_TRC_MAX_LINE_SIZE];
|
|
|
|
/* 1.0 file format does not have version info or column line,
|
|
* so assume that until told otherwise
|
|
*/
|
|
int major = 1;
|
|
int minor = 0;
|
|
|
|
peak_trc_state_t* state = (peak_trc_state_t*)wth->priv;
|
|
|
|
#ifdef PEAK_TRC_DEBUG
|
|
peak_trc_debug_printf("%s: Trying peak_trc file decoder\n", G_STRFUNC);
|
|
#endif
|
|
|
|
/* Initial start time until we find it in the header */
|
|
state->trace_start = time(NULL);
|
|
state->trace_start_nsec = 0;
|
|
|
|
state->file_version_major = 1;
|
|
state->file_version_minor = 0;
|
|
|
|
/* Initialize columns to "invalid" */
|
|
for (int i = 0; i < Col_Invalid; i++)
|
|
state->column_positions[i] = Col_Invalid;
|
|
|
|
/* Bail if the end of the file is here and it hasn't been determined it's a PEAK file */
|
|
while (!file_eof(wth->fh))
|
|
{
|
|
seek_off = file_tell(wth->fh);
|
|
#ifdef PEAK_TRC_DEBUG
|
|
peak_trc_debug_printf("%s: Starting parser at offset %" PRIi64 "\n", G_STRFUNC, seek_off);
|
|
#endif
|
|
if (file_gets(line_buffer, PEAK_TRC_MAX_LINE_SIZE, wth->fh) == NULL)
|
|
{
|
|
/* Error reading file, bail out */
|
|
*err = file_error(wth->fh, err_info);
|
|
if (*err != 0 && *err != WTAP_ERR_SHORT_READ)
|
|
return WTAP_OPEN_ERROR;
|
|
|
|
return WTAP_OPEN_NOT_MINE;
|
|
}
|
|
|
|
g_strstrip(line_buffer);
|
|
size_t line_len = strlen(line_buffer);
|
|
|
|
/* Ignore empty lines */
|
|
if (line_len == 0)
|
|
continue;
|
|
|
|
/* The intention here is to get through all of the "header" which consists of
|
|
* "commented" lines (notated with a ;)
|
|
* Once the header is complete, parsing can be confident it's a PEAK file
|
|
*/
|
|
if (line_buffer[0] != ';')
|
|
{
|
|
/* Two possibilities:
|
|
* 1. This is the start of the actual packet data and header parsing is complete
|
|
* 2. This isn't a PEAK file
|
|
*
|
|
* Determine this by if the timestamp has been found
|
|
*/
|
|
if (!found_timestamp)
|
|
return WTAP_OPEN_NOT_MINE;
|
|
|
|
/* Valid PEAK file */
|
|
GString* regex_str = g_string_new(NULL);
|
|
|
|
*offset = seek_off;
|
|
|
|
/* Save off the data found in the header */
|
|
state->file_version_major = (uint8_t)major;
|
|
state->file_version_minor = (uint8_t)minor;
|
|
|
|
/* File version 1.x doesn't explicitly provide column order information */
|
|
if (state->file_version_major == 1)
|
|
{
|
|
state->column_positions[Col_MessageNumber] = 1;
|
|
|
|
switch(state->file_version_minor)
|
|
{
|
|
case 0:
|
|
state->column_positions[Col_TimeOffset_v1_0] = 2;
|
|
state->column_positions[Col_CanId] = 3;
|
|
state->column_positions[Col_DLC] = 4;
|
|
state->column_positions[Col_ErrorOrRtr_v1p0] = 5;
|
|
state->column_positions[Col_Data_v1_0] = 6;
|
|
break;
|
|
case 1:
|
|
state->column_positions[Col_TimeOffset] = 2;
|
|
state->column_positions[Col_Direction] = 3;
|
|
state->column_positions[Col_CanId] = 4;
|
|
state->column_positions[Col_DLC] = 5;
|
|
state->column_positions[Col_ErrorOrRtr_v1p0] = 6;
|
|
state->column_positions[Col_Data] = 7;
|
|
break;
|
|
case 2:
|
|
state->column_positions[Col_TimeOffset] = 2;
|
|
state->column_positions[Col_BusNumber_v1] = 3;
|
|
state->column_positions[Col_Direction] = 4;
|
|
state->column_positions[Col_CanId] = 5;
|
|
state->column_positions[Col_DLC] = 6;
|
|
state->column_positions[Col_ErrorOrRtr_v1p0] = 7;
|
|
state->column_positions[Col_Data] = 8;
|
|
break;
|
|
default:
|
|
state->column_positions[Col_TimeOffset] = 2;
|
|
state->column_positions[Col_BusNumber_v1] = 3;
|
|
state->column_positions[Col_Direction] = 4;
|
|
state->column_positions[Col_CanId] = 5;
|
|
state->column_positions[Col_Reserved] = 6;
|
|
state->column_positions[Col_DLC] = 7;
|
|
state->column_positions[Col_ErrorOrRtr_v1p0] = 8;
|
|
state->column_positions[Col_Data] = 9;
|
|
break;
|
|
}
|
|
|
|
for (int column_index = 0; column_index < Col_Invalid; column_index++)
|
|
{
|
|
for (int col_type = Col_BusNumber; col_type < Col_Invalid; col_type++)
|
|
{
|
|
if (state->column_positions[col_type] == column_index)
|
|
{
|
|
for (peak_trc_column_map_t* item = colmap; item->type != Col_Invalid; item++)
|
|
{
|
|
if ((int)item->type == col_type)
|
|
{
|
|
g_string_append(regex_str, item->regex);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
state->data_matcher = g_regex_new(regex_str->str, (GRegexCompileFlags)(G_REGEX_OPTIMIZE | G_REGEX_RAW), (GRegexMatchFlags)0, NULL);
|
|
g_string_free(regex_str, TRUE);
|
|
}
|
|
|
|
return WTAP_OPEN_MINE;
|
|
}
|
|
|
|
if (g_str_has_prefix(line_buffer, ";$FILEVERSION="))
|
|
{
|
|
if (sscanf(line_buffer, ";$FILEVERSION=%d.%d\r\n", &major, &minor) != 2)
|
|
return WTAP_OPEN_NOT_MINE;
|
|
}
|
|
else if (g_str_has_prefix(line_buffer, ";$STARTTIME="))
|
|
{
|
|
/* $STARTTIME keyword to store the absolute start time of the trace file:
|
|
Format: Floating point, decimal separator is a point.
|
|
Value: Integral part = Number of days that have passed since 30. December 1899.
|
|
Fractional Part = Fraction of a 24-hour day that has elapsed, resolution is 1 millisecond. */
|
|
|
|
gchar** parts = g_strsplit(line_buffer, "=", 2);
|
|
if (parts[1] == NULL)
|
|
{
|
|
/* At this point, the file is probably a PEAK file that is poorly formatted, so bail with an error */
|
|
*err_info = ws_strdup("peak_trc: unable to parse start time");
|
|
g_strfreev(parts);
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
|
|
/* Convert to a more "standard" timestamp */
|
|
double days_since_epoch = g_strtod(parts[1], NULL);
|
|
days_since_epoch -= 25569.0;
|
|
time_t ts = 0;
|
|
struct tm* t = gmtime(&ts);
|
|
t->tm_sec += (int)(days_since_epoch * 24 * 3600);
|
|
state->trace_start = mktime(t);
|
|
state->trace_start_nsec = 0;
|
|
|
|
found_timestamp = true;
|
|
|
|
g_strfreev(parts);
|
|
}
|
|
else if (g_str_has_prefix(line_buffer, ";$COLUMNS="))
|
|
{
|
|
gchar** parts = g_strsplit(line_buffer, "=", 2);
|
|
if (parts[1] == NULL)
|
|
{
|
|
/* At this point, the file is probably a PEAK file that is poorly formatted, so bail with an error */
|
|
*err_info = ws_strdup("peak_trc: unable to parse column definitions");
|
|
g_strfreev(parts);
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
|
|
gchar** columns = g_strsplit(parts[1], ",", 0);
|
|
int column_index = 0;
|
|
for (gchar** iter = columns; *iter != NULL; iter++)
|
|
{
|
|
size_t col_length = strlen(*iter);
|
|
if (col_length > 1) {
|
|
*err_info = ws_strdup_printf("peak_trc: unknown column definition in the $COLUMNS line: '%s'", *iter);
|
|
g_strfreev(columns);
|
|
g_strfreev(parts);
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
if (col_length == 0)
|
|
{
|
|
//Done ?
|
|
break;
|
|
}
|
|
|
|
/* Assign the column value if found */
|
|
for (peak_trc_column_map_t* item = colmap; item->type != Col_Invalid; item++)
|
|
{
|
|
if ((*iter)[0] == item->code)
|
|
{
|
|
// column type matched
|
|
state->column_positions[item->type] = column_index;
|
|
break;
|
|
}
|
|
}
|
|
column_index++;
|
|
}
|
|
|
|
g_strfreev(columns);
|
|
g_strfreev(parts);
|
|
}
|
|
else if (line_len >= 2)
|
|
{
|
|
/* The rest of the "keywords" are separated by whitespace after the initial ';' */
|
|
char* keyword = &line_buffer[1];
|
|
while (iswspace(*keyword))
|
|
keyword++;
|
|
|
|
if (g_str_has_prefix(keyword, "Start time:"))
|
|
{
|
|
/* Use the $STARTTIME if it has already been found */
|
|
if (!found_timestamp)
|
|
{
|
|
int day, month, year, hour, minute, second, millisecond;
|
|
if (sscanf(keyword, "Start time: %d.%d.%d %d:%d:%d.%d", &month, &day, &year, &hour, &minute, &second, &millisecond) != 7)
|
|
{
|
|
*err_info = ws_strdup("peak_trc: unable to parse start time");
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
|
|
struct tm t;
|
|
t.tm_sec = second;
|
|
t.tm_min = minute;
|
|
t.tm_hour = hour;
|
|
t.tm_mday = day;
|
|
t.tm_mon = month-1;
|
|
t.tm_year = year-1900;
|
|
t.tm_wday = 0;
|
|
t.tm_yday = 0;
|
|
t.tm_isdst = -1;
|
|
|
|
state->trace_start = mktime(&t);
|
|
state->trace_start_nsec = millisecond*1000000;
|
|
|
|
found_timestamp = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return WTAP_OPEN_NOT_MINE;
|
|
}
|
|
|
|
static bool peak_trc_read_packet_v1(peak_trc_state_t* state, peak_trc_msg_t* peak_msg, char* line_buffer)
|
|
{
|
|
// pre 2.0 packets use regexp with fixed column order with a regexp
|
|
GMatchInfo* match_info = NULL;
|
|
bool found = g_regex_match(state->data_matcher, line_buffer, 0, &match_info);
|
|
int column_count = g_match_info_get_match_count(match_info);
|
|
if (!found || column_count < 5)
|
|
{
|
|
/* Not a valid v1 packet */
|
|
g_match_info_free(match_info);
|
|
return false;
|
|
}
|
|
|
|
for (int col = Col_BusNumber; col < Col_Invalid; col++)
|
|
{
|
|
if (state->column_positions[col] >= column_count || state->column_positions[col] == Col_Invalid)
|
|
continue;
|
|
|
|
gchar* column_text = g_match_info_fetch(match_info, state->column_positions[col]);
|
|
if (column_text == NULL)
|
|
{
|
|
g_match_info_free(match_info);
|
|
return false;
|
|
}
|
|
|
|
switch (col)
|
|
{
|
|
case Col_Direction:
|
|
if (strcmp(column_text, "Warng") == 0)
|
|
{
|
|
// Warning lines cannot be interpreted in Wireshark needs to be skipped
|
|
g_free(column_text);
|
|
g_match_info_free(match_info);
|
|
return false;
|
|
}
|
|
if (strcmp(column_text, "Error") == 0)
|
|
{
|
|
peak_msg->msg.type = MSG_TYPE_ERR;
|
|
}
|
|
break;
|
|
case Col_CanId:
|
|
peak_msg->msg.id = (guint32)g_ascii_strtoull(column_text, NULL, 16);
|
|
break;
|
|
case Col_ErrorOrRtr_v1p0:
|
|
if (state->file_version_minor == 1 && strcmp(column_text, "RTR") == 0)
|
|
{
|
|
peak_msg->msg.type = MSG_TYPE_STD_RTR;
|
|
}
|
|
break;
|
|
case Col_TimeOffset:
|
|
case Col_TimeOffset_v1_0:
|
|
{
|
|
double tsd = g_ascii_strtod(column_text, NULL) / 1000.0; // parse to seconds
|
|
tsd += state->trace_start;
|
|
peak_msg->msg.ts.secs = (guint64)tsd;
|
|
peak_msg->msg.ts.nsecs = (int)((tsd - (guint64)tsd) * 1000000000);
|
|
}
|
|
break;
|
|
case Col_DLC:
|
|
peak_msg->msg.data.length = (uint8_t)g_ascii_strtoull(column_text, NULL, 10);
|
|
break;
|
|
case Col_BusNumber_v1:
|
|
peak_msg->flags |= PEAK_TRC_MSG_FLAG_BUS_ID;
|
|
peak_msg->bus_id = (uint8_t)g_ascii_strtoull(column_text, NULL, 10);
|
|
break;
|
|
|
|
case Col_Data:
|
|
case Col_Data_v1_0:
|
|
{
|
|
gchar* err_or_rtr = g_match_info_fetch(match_info, state->column_positions[Col_ErrorOrRtr_v1p0]);
|
|
gchar** bytes = g_strsplit(g_strstrip(column_text), " ", CAN_MAX_DLEN);
|
|
// 1.0 format has an ERROR prefix in the data bytes, 1.1 has a separate columns for message type
|
|
if ((g_str_has_prefix(err_or_rtr, "ERROR")) ||
|
|
(state->file_version_minor == 1 && peak_msg->msg.type == MSG_TYPE_ERR))
|
|
{
|
|
peak_msg->msg.type = MSG_TYPE_ERR;
|
|
|
|
/* For LINUX_CAN_ERR the length has to be CAN_MAX_DLEN */
|
|
peak_msg->msg.data.length = CAN_MAX_DLEN;
|
|
/* Clear the data of the message to populate it with SocketCAN meta data */
|
|
memset(peak_msg->msg.data.data, 0, CAN_MAX_DLEN);
|
|
|
|
guint32 old_id = peak_msg->msg.id;
|
|
peak_msg->msg.id = 0;
|
|
// ID: Type of Error Frame
|
|
switch (old_id)
|
|
{
|
|
case 1: // 1 = Bit Error
|
|
peak_msg->msg.id |= CAN_ERR_PROT_BIT;
|
|
break;
|
|
case 2: // 2 = Form Error
|
|
peak_msg->msg.id |= CAN_ERR_PROT_FORM;
|
|
break;
|
|
case 4: // 4 = Stuff Error
|
|
peak_msg->msg.id |= CAN_ERR_PROT_STUFF;
|
|
break;
|
|
}
|
|
|
|
//Translate PEAK error data into SocketCAN meta data for supported error types
|
|
if ((peak_msg->msg.id | (CAN_ERR_PROT_FORM& CAN_ERR_PROT_STUFF)) != 0)
|
|
{
|
|
// Data Byte 0: Direction
|
|
uint8_t byte0 = (uint8_t)g_ascii_strtoull(bytes[0], NULL, 16);
|
|
if (byte0 < 2)
|
|
{
|
|
//0 = Error occurred while sending
|
|
//1 = Error occurred while receiving.
|
|
peak_msg->msg.data.data[2] |= CAN_ERR_PROT_TX;
|
|
}
|
|
|
|
// Data Byte 1: Current Position in Bit Stream
|
|
uint8_t bit_pos = (uint8_t)g_ascii_strtoull(bytes[1], NULL, 16);
|
|
if (bit_pos == 28)
|
|
{
|
|
peak_msg->msg.data.data[2] |= CAN_ERR_PROT_OVERLOAD;
|
|
}
|
|
else
|
|
{
|
|
// magically SocketCAN and PEAK error bit positions are identical with some exceptions
|
|
peak_msg->msg.data.data[3] = bit_pos;
|
|
if (peak_msg->msg.data.data[3] == 23)
|
|
peak_msg->msg.data.data[3] = 0; // Error position not present in SocketCAN
|
|
}
|
|
}
|
|
}
|
|
else if (g_str_has_prefix(err_or_rtr, "RTR") || (state->file_version_minor == 1 && peak_msg->msg.type == MSG_TYPE_STD_RTR))
|
|
{
|
|
peak_msg->msg.type = MSG_TYPE_STD_RTR;
|
|
peak_msg->msg.data.length = 0;
|
|
}
|
|
else
|
|
{
|
|
peak_msg->msg.type = MSG_TYPE_STD;
|
|
if (bytes)
|
|
{
|
|
for (int byte_i = 0; ((byte_i < CAN_MAX_DLEN) && (bytes[byte_i] != 0)); byte_i++)
|
|
peak_msg->msg.data.data[byte_i] = (uint8_t)g_ascii_strtoull(bytes[byte_i], NULL, 16);
|
|
}
|
|
}
|
|
g_strfreev(bytes);
|
|
g_free(err_or_rtr);
|
|
break;
|
|
}
|
|
}
|
|
g_free(column_text);
|
|
}
|
|
|
|
#ifdef PEAK_TRC_DEBUG
|
|
for (int i = 0; i < column_count; i++)
|
|
peak_trc_debug_printf("%d: %s\n", i, g_match_info_fetch(match_info, i));
|
|
#endif
|
|
g_match_info_free(match_info);
|
|
return true;
|
|
}
|
|
|
|
static bool peak_trc_read_packet_v2(peak_trc_state_t* state, peak_trc_msg_t* peak_msg, char* line_buffer)
|
|
{
|
|
int i = 0;
|
|
int column_i = 0;
|
|
int column_start = 0;
|
|
bool last_char_is_ws = true;
|
|
peak_trc_column_type_t current_column = Col_Invalid;
|
|
while (line_buffer[i] != 0)
|
|
{
|
|
bool is_whitespace = iswspace(line_buffer[i]);
|
|
if (current_column == Col_Data)
|
|
{
|
|
// data always the last and could contain spaces
|
|
is_whitespace = (line_buffer[i] == '\r' || line_buffer[i] == '\n');
|
|
}
|
|
|
|
if (is_whitespace)
|
|
{
|
|
if (!last_char_is_ws)
|
|
{
|
|
// column closed -> process data
|
|
gchar* column_text = g_utf8_substring(line_buffer, column_start, i);
|
|
#ifdef PEAK_TRC_DEBUG
|
|
peak_trc_debug_printf("Column %d: %s\n", current_column, column_text);
|
|
#endif
|
|
switch (current_column) {
|
|
case Col_BusNumber:
|
|
peak_msg->flags |= PEAK_TRC_MSG_FLAG_BUS_ID;
|
|
peak_msg->bus_id = (uint8_t)g_ascii_strtoull(column_text, NULL, 10);
|
|
break;
|
|
case Col_Direction:
|
|
break;
|
|
case Col_Data: {
|
|
if (peak_msg->msg.type == MSG_TYPE_ERR)
|
|
{
|
|
// FIXME fill data and msg id
|
|
}
|
|
else
|
|
{
|
|
gchar** bytes = g_strsplit(g_strstrip(column_text), " ", CANFD_MAX_DLEN);
|
|
int byte_i = 0;
|
|
while (byte_i < CANFD_MAX_DLEN) {
|
|
if (bytes[byte_i] == 0)
|
|
break;
|
|
peak_msg->msg.data.data[byte_i] = (uint8_t)g_ascii_strtoull(bytes[byte_i], NULL, 16);
|
|
byte_i++;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case Col_CanId:
|
|
peak_msg->msg.id = (guint)g_ascii_strtoull(column_text, NULL, 16);
|
|
break;
|
|
case Col_DLC:
|
|
peak_msg->msg.data.length = (guint)g_ascii_strtoull(column_text, NULL, 10);
|
|
break;
|
|
case Col_TimeOffset:
|
|
{
|
|
double tsd = g_ascii_strtod(column_text, NULL) / 1000.0; // parse to seconds
|
|
tsd += state->trace_start;
|
|
peak_msg->msg.ts.secs = (guint64)tsd;
|
|
peak_msg->msg.ts.nsecs = (int)((tsd - (guint64)tsd) * 1000000000);
|
|
break;
|
|
}
|
|
case Col_MessageType:
|
|
if (strcmp(column_text, "DT") == 0)
|
|
{
|
|
peak_msg->msg.type = MSG_TYPE_STD;
|
|
}
|
|
else if (strcmp(column_text, "FD") == 0)
|
|
{
|
|
peak_msg->msg.type = MSG_TYPE_STD_FD;
|
|
}
|
|
else if (strcmp(column_text, "FB") == 0)
|
|
{
|
|
// CAN FD data frame with BRS bit set (Bit Rate Switch).
|
|
peak_msg->msg.type = MSG_TYPE_STD_FD; // TODO
|
|
}
|
|
else if (strcmp(column_text, "FE") == 0)
|
|
{
|
|
// CAN FD data frame with ESI bit set (Error State Indicator).
|
|
peak_msg->msg.type = MSG_TYPE_ERR;
|
|
}
|
|
else if (strcmp(column_text, "BI") == 0)
|
|
{
|
|
// CAN FD data frame with both BRS and ESI bits set.
|
|
peak_msg->msg.type = MSG_TYPE_ERR;
|
|
}
|
|
else if (strcmp(column_text, "RR") == 0)
|
|
{
|
|
peak_msg->msg.type = MSG_TYPE_STD_RTR;
|
|
}
|
|
else if (strcmp(column_text, "ST") == 0)
|
|
{
|
|
// TODO: Hardware Status change.
|
|
// Currently not supported in Wireshark
|
|
return false;
|
|
}
|
|
else if (strcmp(column_text, "ER") == 0)
|
|
{
|
|
peak_msg->msg.type = MSG_TYPE_ERR;
|
|
}
|
|
else if (strcmp(column_text, "EC") == 0)
|
|
{
|
|
// TODO: Error Counter change
|
|
// Currently not supported in Wireshark
|
|
return false;
|
|
}
|
|
else if (strcmp(column_text, "EV") == 0)
|
|
{
|
|
// Event. User-defined text, begins directly after bus specifier
|
|
// TODO add support for this event type
|
|
return false;
|
|
}
|
|
break;
|
|
case Col_MessageNumber:
|
|
case Col_Reserved:
|
|
case Col_Invalid:
|
|
break;
|
|
/* Version 1 column types ignored */
|
|
case Col_ErrorOrRtr_v1p0:
|
|
case Col_BusNumber_v1:
|
|
case Col_TimeOffset_v1_0:
|
|
case Col_Data_v1_0:
|
|
break;
|
|
}
|
|
g_free(column_text);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// non whitespace read
|
|
if (last_char_is_ws)
|
|
{
|
|
for (int lut_i = 0; lut_i < Col_Invalid; lut_i++)
|
|
{
|
|
if (state->column_positions[lut_i] == column_i)
|
|
{
|
|
current_column = lut_i;
|
|
break;
|
|
}
|
|
}
|
|
column_i++;
|
|
column_start = i;
|
|
}
|
|
}
|
|
last_char_is_ws = is_whitespace;
|
|
i++;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
peak_trc_read_packet(wtap *wth, FILE_T fh, peak_trc_state_t* state,
|
|
wtap_rec *rec, int *err, gchar **err_info,
|
|
gint64 *data_offset)
|
|
{
|
|
peak_trc_msg_t peak_msg = {0};
|
|
char line_buffer[PEAK_TRC_MAX_LINE_SIZE];
|
|
|
|
while (!file_eof(fh))
|
|
{
|
|
if (data_offset)
|
|
*data_offset = file_tell(fh);
|
|
|
|
/* Read a line */
|
|
if (file_gets(line_buffer, PEAK_TRC_MAX_LINE_SIZE, fh) == NULL)
|
|
{
|
|
*err = file_error(fh, err_info);
|
|
return false;
|
|
}
|
|
|
|
/* Ignore empty or commented lines */
|
|
if ((strlen(line_buffer) == 0) ||
|
|
(line_buffer[0] == ';'))
|
|
continue;
|
|
|
|
if (state->file_version_major >= 2)
|
|
{
|
|
if (!peak_trc_read_packet_v2(state, &peak_msg, line_buffer))
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
if (!peak_trc_read_packet_v1(state, &peak_msg, line_buffer))
|
|
continue;
|
|
}
|
|
|
|
if (peak_msg.msg.id > 0x7FF)
|
|
{
|
|
//Translate from 11-bit to 32-bit IDs
|
|
switch (peak_msg.msg.type)
|
|
{
|
|
case MSG_TYPE_STD_FD:
|
|
peak_msg.msg.type = MSG_TYPE_EXT_FD;
|
|
break;
|
|
case MSG_TYPE_STD:
|
|
peak_msg.msg.type = MSG_TYPE_EXT;
|
|
break;
|
|
case MSG_TYPE_STD_RTR:
|
|
peak_msg.msg.type = MSG_TYPE_EXT_RTR;
|
|
break;
|
|
default:
|
|
//Ignore other types
|
|
break;
|
|
}
|
|
}
|
|
|
|
return peak_trc_write_packet(wth, rec, &peak_msg, err, err_info);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool
|
|
peak_trc_read(wtap *wth, wtap_rec *rec, int *err, char **err_info,
|
|
int64_t *data_offset)
|
|
{
|
|
peak_trc_state_t* state = (peak_trc_state_t*)wth->priv;
|
|
|
|
#ifdef PEAK_TRC_DEBUG
|
|
peak_trc_debug_printf("%s: Try reading at offset %" PRIi64 "\n", G_STRFUNC, file_tell(wth->fh));
|
|
#endif
|
|
|
|
return peak_trc_read_packet(wth, wth->fh, state, rec, err, err_info, data_offset);
|
|
}
|
|
|
|
static bool peak_trc_seek_read(wtap *wth, int64_t seek_off, wtap_rec *rec, int *err, char **err_info)
|
|
{
|
|
#ifdef PEAK_TRC_DEBUG
|
|
peak_trc_debug_printf("%s: Read at offset %" PRIi64 "\n", G_STRFUNC, seek_off);
|
|
#endif
|
|
peak_trc_state_t* state = (peak_trc_state_t*)wth->priv;
|
|
|
|
if (file_seek(wth->random_fh, seek_off, SEEK_SET, err) == -1)
|
|
{
|
|
*err = errno;
|
|
*err_info = g_strdup(g_strerror(errno));
|
|
|
|
return false;
|
|
}
|
|
|
|
return peak_trc_read_packet(wth, wth->random_fh, state, rec, err, err_info, NULL);
|
|
}
|
|
|
|
static void peak_trc_close(wtap* wth)
|
|
{
|
|
peak_trc_state_t* state = (peak_trc_state_t*)wth->priv;
|
|
if (state != NULL)
|
|
{
|
|
if (state->data_matcher != NULL)
|
|
g_regex_unref(state->data_matcher);
|
|
}
|
|
}
|
|
|
|
wtap_open_return_val
|
|
peak_trc_open(wtap* wth, int* err, char** err_info)
|
|
{
|
|
gint64 data_offset = 0;
|
|
|
|
/* wth->priv stores a pointer to the general file properties.
|
|
* It it updated when the header data is parsed */
|
|
wth->priv = g_new0(peak_trc_state_t, 1);
|
|
|
|
wtap_open_return_val open_val = peak_trc_parse(wth, &data_offset, err, err_info);
|
|
if (open_val != WTAP_OPEN_MINE)
|
|
{
|
|
peak_trc_close(wth);
|
|
g_free(wth->priv);
|
|
wth->priv = NULL;
|
|
return open_val;
|
|
}
|
|
|
|
#ifdef PEAK_TRC_DEBUG
|
|
peak_trc_debug_printf("%s: This is our file\n", G_STRFUNC);
|
|
#endif
|
|
|
|
/* Go to the start of the real packet data since header is now done */
|
|
if (file_seek(wth->fh, data_offset, SEEK_SET, err) == -1)
|
|
{
|
|
*err = errno;
|
|
*err_info = g_strdup(g_strerror(errno));
|
|
return WTAP_OPEN_ERROR;
|
|
}
|
|
|
|
wtap_set_as_socketcan(wth, peak_trc_file_type_subtype, WTAP_TSPREC_USEC);
|
|
wth->subtype_read = peak_trc_read;
|
|
wth->subtype_seek_read = peak_trc_seek_read;
|
|
wth->subtype_close = peak_trc_close;
|
|
|
|
return WTAP_OPEN_MINE;
|
|
}
|
|
|
|
static const struct supported_block_type peak_trc_blocks_supported[] = {
|
|
/*
|
|
* We support packet blocks, with no comments or other options.
|
|
*/
|
|
{ WTAP_BLOCK_PACKET, MULTIPLE_BLOCKS_SUPPORTED, NO_OPTIONS_SUPPORTED }
|
|
};
|
|
|
|
static const struct file_type_subtype_info peak_trc_info = {
|
|
"PEAK CAN TRC file", "peak-trc", "trc", NULL,
|
|
false, BLOCKS_SUPPORTED(peak_trc_blocks_supported),
|
|
NULL, NULL, NULL
|
|
};
|
|
|
|
void register_peak_trc(void)
|
|
{
|
|
peak_trc_file_type_subtype = wtap_register_file_type_subtype(&peak_trc_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:
|
|
*/
|