Coverage Report

Created: 2026-11-12 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wireshark/wiretap/peak-trc.c
Line
Count
Source
1
/* peak_trc.c
2
 *
3
 * Wiretap Library
4
 * Copyright (c) 1998 by Gilbert Ramirez <gram@alumni.rice.edu>
5
 *
6
 * Support for TRC log file format generated by several CAN tools from PEAK Gmbh
7
 * Copyright (c) 2024 by Miklos Marton <martonmiklosqdev@gmail.com>
8
 *
9
 * File format description:
10
 * https://www.peak-system.com/produktcd/Pdf/English/PEAK_CAN_TRC_File_Format.pdf
11
 *
12
 * SPDX-License-Identifier: GPL-2.0-or-later
13
 */
14
15
#include "config.h"
16
0
#define WS_LOG_DOMAIN "peak-trc"
17
#include <wireshark.h>
18
#include <file_wrappers.h>
19
#include <epan/dissectors/packet-socketcan.h>
20
#include <wctype.h>
21
#include <string.h>
22
#include <stdlib.h>
23
#include <errno.h>
24
#include <time.h>
25
#include "peak-trc.h"
26
#include <wsutil/strtoi.h>
27
#include "socketcan.h"
28
29
0
#define PEAK_TRC_MAX_LINE_SIZE 4096 // J1939 logs could contain long lines
30
31
typedef enum {
32
    FileVersion,
33
    StartTime,
34
    ColumnHeader,
35
    Comment,
36
    Data
37
} peak_trc_parse_state_t;
38
39
typedef enum {
40
    Col_BusNumber, // 0
41
    Col_BusNumber_v1, // 1
42
    Col_Direction, // 2
43
    Col_CanId, // 3
44
    Col_DLC_l, // 4 l (lowercase ā€˜L’) Data Length(0 - 64)
45
    Col_DLC_L, // 5 L Data Length Code (CAN: 0..8; CAN FD: 0..15)
46
    Col_MessageNumber, // 6
47
    Col_TimeOffset_v1_0, // 7
48
    Col_TimeOffset, // 8
49
    Col_Reserved, // 9
50
    Col_MessageType, // 10
51
    Col_ErrorOrRtr_v1p0, // 11
52
    // keep it to the last one to get the
53
    Col_Data_v1_0, // 12
54
    Col_Data, // 13
55
    Col_Invalid // 14
56
} peak_trc_column_type_t;
57
58
typedef struct {
59
    peak_trc_column_type_t type;
60
    char code;
61
    char* regex;
62
} peak_trc_column_map_t;
63
64
typedef struct {
65
    time_t trace_start;
66
    time_t trace_start_nsec;
67
68
    uint8_t column_positions[Col_Invalid];
69
70
    uint8_t file_version_major, file_version_minor;
71
    GRegex* data_matcher;
72
} peak_trc_state_t;
73
74
static int peak_trc_file_type_subtype = -1;
75
76
static const peak_trc_column_map_t colmap[] = {
77
    {Col_BusNumber, 'B', "\\s*(([0-9]*)|-)"},
78
    /* This isn't a real column type, so give it a "type" that shouldn't be used */
79
    {Col_BusNumber_v1, 1, "\\s*([0-9]*)"},
80
    {Col_Direction, 'd', "\\s*(Rx|Tx|Warng|Error)"},
81
    /* This isn't a real column type, so give it a "type" that shouldn't be used */
82
    {Col_Data_v1_0, 2, "\\s?(((--|[0-9A-F]{2})\\s?)*)"},
83
    {Col_Data, 'D', "\\s?((((--|[0-9A-F]{2})\\s?))*)"},
84
    {Col_CanId, 'I', "\\s*([0-9A-F]*)"},
85
    {Col_DLC_l, 'l', "\\s*([0-9]*)"},
86
    {Col_DLC_L, 'L', "\\s*([0-9]*)"}, /* https://www.peak-system.com/documentation/API/PCAN-Basic.Net/html/3ac7b0e3-f9df-c30e-d4fe-bcb63e5295bb.htm */
87
    {Col_MessageNumber, 'N', "\\s*([0-9]*)\\)"},
88
    {Col_TimeOffset, 'O', "\\s*([0-9]*\\.[0-9]*)"},
89
    /* This isn't a real column type, so give it a "type" that shouldn't be used */
90
    {Col_TimeOffset_v1_0, 3, "\\s*([0-9]*)"},
91
    {Col_Reserved, 'R', "\\s*(-)"},
92
    {Col_MessageType, 'T', "\\s*([A-Z]{2})"},
93
    /* This isn't a real column, so give it a "type" that shouldn't be used */
94
    {Col_ErrorOrRtr_v1p0, 4, "\\s*(ERROR|RTR)?"},
95
    {Col_Invalid, 0, ""},
96
    };
97
98
void register_peak_trc(void);
99
100
static wtap_open_return_val
101
peak_trc_parse(wtap* wth, peak_trc_state_t* state, int64_t* offset, int* err, char** err_info)
102
0
{
103
0
    int64_t seek_off = 0;
104
0
    bool found_timestamp = false;
105
0
    char line_buffer[PEAK_TRC_MAX_LINE_SIZE];
106
107
    /* 1.0 file format does not have version info or column line,
108
     * so assume that until told otherwise
109
     */
110
0
    int major = 1;
111
0
    int minor = 0;
112
113
0
    ws_debug("%s: Trying peak_trc file decoder\n", G_STRFUNC);
114
115
    /* Initial start time until we find it in the header */
116
0
    state->trace_start = time(NULL);
117
0
    state->trace_start_nsec = 0;
118
119
0
    state->file_version_major = 1;
120
0
    state->file_version_minor = 0;
121
122
    /* Initialize columns to "invalid" */
123
0
    for (int i = 0; i < Col_Invalid; i++)
124
0
        state->column_positions[i] = Col_Invalid;
125
126
    /* Bail if the end of the file is here and it hasn't been determined it's a PEAK file */
127
0
    while (!file_eof(wth->fh))
128
0
    {
129
0
        seek_off = file_tell(wth->fh);
130
131
0
        ws_debug("%s: Starting parser at offset %" PRIi64 "\n", G_STRFUNC, seek_off);
132
133
0
        if (file_gets(line_buffer, PEAK_TRC_MAX_LINE_SIZE, wth->fh) == NULL)
134
0
        {
135
            /* Error reading file, bail out */
136
0
            *err = file_error(wth->fh, err_info);
137
0
            if (*err != 0 && *err != WTAP_ERR_SHORT_READ)
138
0
                return WTAP_OPEN_ERROR;
139
140
0
            return WTAP_OPEN_NOT_MINE;
141
0
        }
142
143
0
        g_strstrip(line_buffer);
144
0
        size_t line_len = strlen(line_buffer);
145
146
        /* Ignore empty lines */
147
0
        if (line_len == 0)
148
0
            continue;
149
150
        /* The intention here is to get through all of the "header" which consists of
151
         * "commented" lines (notated with a ;)
152
         * Once the header is complete, parsing can be confident it's a PEAK file
153
         */
154
0
        if (line_buffer[0] != ';')
155
0
        {
156
            /* Two possibilities:
157
             * 1. This is the start of the actual packet data and header parsing is complete
158
             * 2. This isn't a PEAK file
159
             *
160
             * Determine this by if the timestamp has been found
161
             */
162
0
            if (!found_timestamp)
163
0
                return WTAP_OPEN_NOT_MINE;
164
165
            /* Valid PEAK file */
166
0
            *offset = seek_off;
167
168
            /* Save off the data found in the header */
169
0
            state->file_version_major = (uint8_t)major;
170
0
            state->file_version_minor = (uint8_t)minor;
171
172
            /* File version 1.x doesn't explicitly provide column order information */
173
0
            if (state->file_version_major == 1)
174
0
            {
175
0
                state->column_positions[Col_MessageNumber] = 1;
176
177
0
                switch(state->file_version_minor)
178
0
                {
179
0
                case 0:
180
0
                    state->column_positions[Col_TimeOffset_v1_0] = 2;
181
0
                    state->column_positions[Col_CanId] = 3;
182
0
                    state->column_positions[Col_DLC_l] = 4;
183
0
                    state->column_positions[Col_ErrorOrRtr_v1p0] = 5;
184
0
                    state->column_positions[Col_Data_v1_0] = 6;
185
0
                    break;
186
0
                case 1:
187
0
                    state->column_positions[Col_TimeOffset] = 2;
188
0
                    state->column_positions[Col_Direction] = 3;
189
0
                    state->column_positions[Col_CanId] = 4;
190
0
                    state->column_positions[Col_DLC_l] = 5;
191
0
                    state->column_positions[Col_ErrorOrRtr_v1p0] = 6;
192
0
                    state->column_positions[Col_Data] = 7;
193
0
                    break;
194
0
                case 2:
195
0
                    state->column_positions[Col_TimeOffset] = 2;
196
0
                    state->column_positions[Col_BusNumber_v1] = 3;
197
0
                    state->column_positions[Col_Direction] = 4;
198
0
                    state->column_positions[Col_CanId] = 5;
199
0
                    state->column_positions[Col_DLC_l] = 6;
200
0
                    state->column_positions[Col_ErrorOrRtr_v1p0] = 7;
201
0
                    state->column_positions[Col_Data] = 8;
202
0
                    break;
203
0
                default:
204
0
                    state->column_positions[Col_TimeOffset] = 2;
205
0
                    state->column_positions[Col_BusNumber_v1] = 3;
206
0
                    state->column_positions[Col_Direction] = 4;
207
0
                    state->column_positions[Col_CanId] = 5;
208
0
                    state->column_positions[Col_Reserved] = 6;
209
0
                    state->column_positions[Col_DLC_l] = 7;
210
0
                    state->column_positions[Col_ErrorOrRtr_v1p0] = 8;
211
0
                    state->column_positions[Col_Data] = 9;
212
0
                    break;
213
0
                }
214
215
0
                GString* regex_str = g_string_new(NULL);
216
0
                for (int column_index = 0; column_index < Col_Invalid; column_index++)
217
0
                {
218
0
                    for (int col_type = Col_BusNumber; col_type < Col_Invalid; col_type++)
219
0
                    {
220
0
                        if (state->column_positions[col_type] == column_index)
221
0
                        {
222
0
                            for (const peak_trc_column_map_t * item = colmap; item->type != Col_Invalid; item++)
223
0
                            {
224
0
                                if ((int)item->type == col_type)
225
0
                                {
226
0
                                    g_string_append(regex_str, item->regex);
227
0
                                    break;
228
0
                                }
229
0
                            }
230
0
                        }
231
0
                    }
232
0
                }
233
234
0
                state->data_matcher = g_regex_new(regex_str->str, (GRegexCompileFlags)(G_REGEX_OPTIMIZE | G_REGEX_RAW), (GRegexMatchFlags)0, NULL);
235
0
                g_string_free(regex_str, TRUE);
236
0
            }
237
238
0
            return WTAP_OPEN_MINE;
239
0
        }
240
241
0
        if (g_str_has_prefix(line_buffer, ";$FILEVERSION="))
242
0
        {
243
            /* Parse ";$FILEVERSION=major.minor" */
244
0
            const char *p = line_buffer + strlen(";$FILEVERSION=");
245
0
            const char *end;
246
0
            if (!ws_strtoi(p, &end, &major) || *end != '.')
247
0
                return WTAP_OPEN_NOT_MINE;
248
0
            p = end + 1;
249
0
            if (!ws_strtoi(p, NULL, &minor))
250
0
                return WTAP_OPEN_NOT_MINE;
251
0
        }
252
0
        else if (g_str_has_prefix(line_buffer, ";$STARTTIME="))
253
0
        {
254
            /* $STARTTIME keyword to store the absolute start time of the trace file:
255
                Format: Floating point, decimal separator is a point.
256
                Value: Integral part = Number of days that have passed since 30. December 1899.
257
                Fractional Part = Fraction of a 24-hour day that has elapsed, resolution is 1 millisecond. */
258
259
0
            char** parts = g_strsplit(line_buffer, "=", 2);
260
0
            if (parts[1] == NULL)
261
0
            {
262
                /* At this point, the file is probably a PEAK file that is poorly formatted, so bail with an error */
263
0
                *err_info = ws_strdup("peak_trc: unable to parse start time");
264
0
                g_strfreev(parts);
265
0
                return WTAP_OPEN_ERROR;
266
0
            }
267
268
            /* Convert to a more "standard" timestamp */
269
0
            double days_since_epoch = g_strtod(parts[1], NULL);
270
0
            days_since_epoch -= 25569.0;
271
0
            time_t ts = 0;
272
0
            struct tm* start_tm = gmtime(&ts);
273
0
            if (start_tm == NULL) // Extremely unlikely
274
0
            {
275
0
                *err_info = ws_strdup("peak_trc: unable to convert start time");
276
0
                g_strfreev(parts);
277
0
                return WTAP_OPEN_ERROR;
278
0
            }
279
0
            start_tm->tm_sec += (int)(days_since_epoch * 24 * 3600);
280
0
            state->trace_start = mktime(start_tm);
281
0
            state->trace_start_nsec = 0;
282
283
0
            found_timestamp = true;
284
285
0
            g_strfreev(parts);
286
0
        }
287
0
        else if (g_str_has_prefix(line_buffer, ";$COLUMNS="))
288
0
        {
289
0
            char** parts = g_strsplit(line_buffer, "=", 2);
290
0
            if (parts[1] == NULL)
291
0
            {
292
                /* At this point, the file is probably a PEAK file that is poorly formatted, so bail with an error */
293
0
                *err_info = ws_strdup("peak_trc: unable to parse column definitions");
294
0
                g_strfreev(parts);
295
0
                return WTAP_OPEN_ERROR;
296
0
            }
297
298
0
            char** columns = g_strsplit(parts[1], ",", 0);
299
0
            int column_index = 0;
300
0
            for (char** iter = columns; *iter != NULL; iter++)
301
0
            {
302
0
                size_t col_length = strlen(*iter);
303
0
                if (col_length > 1) {
304
0
                    *err_info = ws_strdup_printf("peak_trc: unknown column definition in the $COLUMNS line: '%s'", *iter);
305
0
                    g_strfreev(columns);
306
0
                    g_strfreev(parts);
307
0
                    return WTAP_OPEN_ERROR;
308
0
                }
309
0
                if (col_length == 0)
310
0
                {
311
                    //Done ?
312
0
                    break;
313
0
                }
314
315
                /* Assign the column value if found */
316
0
                for (const peak_trc_column_map_t* item = colmap; item->type != Col_Invalid; item++)
317
0
                {
318
0
                    if ((*iter)[0] == item->code)
319
0
                    {
320
                        // column type matched
321
0
                        state->column_positions[item->type] = column_index;
322
0
                        break;
323
0
                    }
324
0
                }
325
0
                column_index++;
326
0
            }
327
328
0
            g_strfreev(columns);
329
0
            g_strfreev(parts);
330
0
        }
331
0
        else if (line_len >= 2)
332
0
        {
333
            /* The rest of the "keywords" are separated by whitespace after the initial ';' */
334
0
            char* keyword = &line_buffer[1];
335
0
            while (g_ascii_isspace(*keyword))
336
0
                keyword++;
337
338
0
            if (g_str_has_prefix(keyword, "Start time:"))
339
0
            {
340
                /* Use the $STARTTIME if it has already been found */
341
0
                if (!found_timestamp)
342
0
                {
343
0
                    int day, month, year, hour, minute, second, millisecond;
344
                    /* Parse "Start time: month.day.year hour:minute:second.millisecond" */
345
0
                    {
346
0
                        const char *p = keyword + strlen("Start time: ");
347
0
                        const char *end;
348
0
                        bool ok = ws_strtoi(p, &end, &month)  && *end == '.' &&
349
0
                                  ws_strtoi(end+1, &end, &day)  && *end == '.' &&
350
0
                                  ws_strtoi(end+1, &end, &year) && *end == ' ' &&
351
0
                                  ws_strtoi(end+1, &end, &hour) && *end == ':' &&
352
0
                                  ws_strtoi(end+1, &end, &minute) && *end == ':' &&
353
0
                                  ws_strtoi(end+1, &end, &second) && *end == '.' &&
354
0
                                  ws_strtoi(end+1, NULL, &millisecond);
355
0
                        if (!ok)
356
0
                        {
357
0
                            *err_info = ws_strdup("peak_trc: unable to parse start time");
358
0
                            return WTAP_OPEN_ERROR;
359
0
                        }
360
0
                    }
361
362
0
                    struct tm t;
363
0
                    t.tm_sec = second;
364
0
                    t.tm_min = minute;
365
0
                    t.tm_hour = hour;
366
0
                    t.tm_mday = day;
367
0
                    t.tm_mon = month-1;
368
0
                    t.tm_year = year-1900;
369
0
                    t.tm_wday = 0;
370
0
                    t.tm_yday = 0;
371
0
                    t.tm_isdst = -1;
372
373
0
                    state->trace_start = mktime(&t);
374
0
                    state->trace_start_nsec = millisecond*1000000;
375
376
0
                    found_timestamp = true;
377
0
                }
378
0
            }
379
0
        }
380
0
    }
381
382
0
    return WTAP_OPEN_NOT_MINE;
383
0
}
384
385
static bool peak_trc_read_packet_v1(wtap* wth, peak_trc_state_t* state, wtap_can_msg_t* peak_msg, char* line_buffer)
386
0
{
387
    // pre 2.0 packets use regexp with fixed column order with a regexp
388
0
    GMatchInfo* match_info = NULL;
389
0
    bool found = g_regex_match(state->data_matcher, line_buffer, 0, &match_info);
390
0
    int column_count = g_match_info_get_match_count(match_info);
391
0
    if (!found || column_count < 5)
392
0
    {
393
        /* Not a valid v1 packet */
394
0
        g_match_info_free(match_info);
395
0
        return false;
396
0
    }
397
398
0
    for (int col = Col_BusNumber; col < Col_Invalid; col++)
399
0
    {
400
0
        if (state->column_positions[col] >= column_count || state->column_positions[col] == Col_Invalid)
401
0
            continue;
402
403
0
        char* column_text = g_match_info_fetch(match_info, state->column_positions[col]);
404
0
        if (column_text == NULL)
405
0
        {
406
0
            g_match_info_free(match_info);
407
0
            return false;
408
0
        }
409
410
0
        switch (col)
411
0
        {
412
0
        case Col_Direction:
413
0
            if (strcmp(column_text, "Warng") == 0)
414
0
            {
415
                // Warning lines cannot be interpreted in Wireshark needs to be skipped
416
0
                g_free(column_text);
417
0
                g_match_info_free(match_info);
418
0
                return false;
419
0
            }
420
0
            if (strcmp(column_text, "Error") == 0)
421
0
            {
422
0
                peak_msg->type = MSG_TYPE_ERR;
423
0
            }
424
0
            break;
425
0
        case Col_CanId:
426
0
            peak_msg->id = (uint32_t)g_ascii_strtoull(column_text, NULL, 16);
427
0
            break;
428
0
        case Col_ErrorOrRtr_v1p0:
429
0
            if (state->file_version_minor == 1 && strcmp(column_text, "RTR") == 0)
430
0
            {
431
0
                peak_msg->type = MSG_TYPE_STD_RTR;
432
0
            }
433
0
            break;
434
0
        case Col_TimeOffset:
435
0
        case Col_TimeOffset_v1_0:
436
0
        {
437
0
            double tsd = g_ascii_strtod(column_text, NULL) / 1000.0; // parse to seconds
438
0
            tsd += state->trace_start;
439
0
            peak_msg->ts.secs = (uint64_t)tsd;
440
0
            peak_msg->ts.nsecs = (int)((tsd - (uint64_t)tsd) * 1000000000);
441
0
        }
442
0
        break;
443
0
        case Col_DLC_l:
444
0
            peak_msg->data.length = (uint8_t)g_ascii_strtoull(column_text, NULL, 10);
445
0
            break;
446
0
        case Col_BusNumber_v1:
447
0
        {
448
0
            char* interface_name = wmem_strdup_printf(NULL, "interface%s", column_text);
449
0
            peak_msg->interface_id = wtap_socketcan_find_or_create_new_interface(wth, interface_name);
450
0
            wmem_free(NULL, interface_name);
451
0
            break;
452
0
        }
453
454
0
        case Col_Data:
455
0
        case Col_Data_v1_0:
456
0
        {
457
0
            char* err_or_rtr = g_match_info_fetch(match_info, state->column_positions[Col_ErrorOrRtr_v1p0]);
458
0
            char** bytes = g_strsplit(g_strstrip(column_text), " ", CAN_MAX_DLEN);
459
            // 1.0 format has an ERROR prefix in the data bytes, 1.1 has a separate columns for message type
460
0
            if ((g_str_has_prefix(err_or_rtr, "ERROR")) ||
461
0
                (state->file_version_minor == 1 && peak_msg->type == MSG_TYPE_ERR))
462
0
            {
463
0
                peak_msg->type = MSG_TYPE_ERR;
464
465
                /* For LINUX_CAN_ERR the length has to be CAN_MAX_DLEN */
466
0
                peak_msg->data.length = CAN_MAX_DLEN;
467
                /* Clear the data of the message to populate it with SocketCAN meta data */
468
0
                memset(peak_msg->data.data, 0, CAN_MAX_DLEN);
469
470
0
                uint32_t old_id = peak_msg->id;
471
0
                peak_msg->id = 0;
472
                // ID: Type of Error Frame
473
0
                switch (old_id)
474
0
                {
475
0
                case 1: // 1 = Bit Error
476
0
                    peak_msg->id |= CAN_ERR_PROT_BIT;
477
0
                    break;
478
0
                case 2: // 2 = Form Error
479
0
                    peak_msg->id |= CAN_ERR_PROT_FORM;
480
0
                    break;
481
0
                case 4: // 4 = Stuff Error
482
0
                    peak_msg->id |= CAN_ERR_PROT_STUFF;
483
0
                    break;
484
0
                }
485
486
                // There should be 4 bytes of data in an error message.
487
                // The first two have information that can become metadata
488
                // XXX - Should we reject the message entirely if there's fewer
489
                // than 2, or 4, bytes of data?
490
                //Translate PEAK error data into SocketCAN meta data for supported error types
491
0
                if (bytes && g_strv_length(bytes) >= 2 && (peak_msg->id | (CAN_ERR_PROT_FORM& CAN_ERR_PROT_STUFF)) != 0)
492
0
                {
493
                    // Data Byte 0: Direction
494
0
                    uint8_t byte0 = (uint8_t)g_ascii_strtoull(bytes[0], NULL, 16);
495
0
                    if (byte0 < 2)
496
0
                    {
497
                        //0 = Error occurred while sending
498
                        //1 = Error occurred while receiving.
499
0
                        peak_msg->data.data[2] |= CAN_ERR_PROT_TX;
500
0
                    }
501
502
                    // Data Byte 1: Current Position in Bit Stream
503
0
                    uint8_t bit_pos = (uint8_t)g_ascii_strtoull(bytes[1], NULL, 16);
504
0
                    if (bit_pos == 28)
505
0
                    {
506
0
                        peak_msg->data.data[2] |= CAN_ERR_PROT_OVERLOAD;
507
0
                    }
508
0
                    else
509
0
                    {
510
                        // magically SocketCAN and PEAK error bit positions are identical with some exceptions
511
0
                        peak_msg->data.data[3] = bit_pos;
512
0
                        if (peak_msg->data.data[3] == 23)
513
0
                            peak_msg->data.data[3] = 0; // Error position not present in SocketCAN
514
0
                    }
515
0
                }
516
0
            }
517
0
            else if (g_str_has_prefix(err_or_rtr, "RTR") || (state->file_version_minor == 1 && peak_msg->type == MSG_TYPE_STD_RTR))
518
0
            {
519
0
                peak_msg->type = MSG_TYPE_STD_RTR;
520
0
                peak_msg->data.length = 0;
521
0
            }
522
0
            else
523
0
            {
524
0
                peak_msg->type = MSG_TYPE_STD;
525
0
                if (bytes)
526
0
                {
527
0
                    for (int byte_i = 0; ((byte_i < CAN_MAX_DLEN) && (bytes[byte_i] != 0)); byte_i++)
528
0
                        peak_msg->data.data[byte_i] = (uint8_t)g_ascii_strtoull(bytes[byte_i], NULL, 16);
529
0
                }
530
0
            }
531
0
            g_strfreev(bytes);
532
0
            g_free(err_or_rtr);
533
0
            break;
534
0
        }
535
0
        }
536
0
        g_free(column_text);
537
0
    }
538
539
#ifdef WS_DEBUG
540
    for (int i = 0; i < column_count; i++) {
541
        char *match_text = g_match_info_fetch(match_info, i);
542
        ws_debug("%d: %s\n", i, match_text);
543
        g_free(match_text);
544
    }
545
#endif
546
547
0
    g_match_info_free(match_info);
548
0
    return true;
549
0
}
550
551
uint8_t
552
dlc_to_data_length(uint8_t dlc)
553
0
{
554
0
    if (dlc <= 8)
555
0
        return dlc;
556
557
0
    switch (dlc)
558
0
    {
559
0
    case 9:  return 12;
560
0
    case 10: return 16;
561
0
    case 11: return 20;
562
0
    case 12: return 24;
563
0
    case 13: return 32;
564
0
    case 14: return 48;
565
0
    case 15: return 64;
566
0
    default: return 0;
567
0
    }
568
0
}
569
570
static bool peak_trc_read_packet_v2(wtap* wth, peak_trc_state_t* state, wtap_can_msg_t* peak_msg, char* line_buffer)
571
0
{
572
0
    int i = 0;
573
0
    int column_i = 0;
574
0
    int column_start = 0;
575
0
    bool last_char_is_ws = true;
576
0
    peak_trc_column_type_t current_column = Col_Invalid;
577
0
    while (line_buffer[i] != 0)
578
0
    {
579
0
        bool is_whitespace = g_ascii_isspace(line_buffer[i]);
580
0
        if (current_column == Col_Data)
581
0
        {
582
            // data always the last and could contain spaces
583
0
            is_whitespace = (line_buffer[i] == '\r' || line_buffer[i] == '\n');
584
0
        }
585
586
0
        if (is_whitespace)
587
0
        {
588
0
            if (!last_char_is_ws)
589
0
            {
590
                // column closed -> process data
591
0
                char* column_text = g_utf8_make_valid(&line_buffer[column_start], i - column_start);
592
593
0
                ws_debug("Column %d: %s", current_column, column_text);
594
595
0
                switch (current_column) {
596
0
                case Col_BusNumber:
597
0
                {
598
0
                    char* interface_name = wmem_strdup_printf(NULL, "interface%s", column_text);
599
0
                    peak_msg->interface_id = wtap_socketcan_find_or_create_new_interface(wth, interface_name);
600
0
                    wmem_free(NULL, interface_name);
601
0
                    break;
602
0
                }
603
0
                case Col_Direction:
604
0
                    break;
605
0
                case Col_Data: {
606
0
                    if (peak_msg->type == MSG_TYPE_ERR)
607
0
                    {
608
                        // FIXME fill data and msg id
609
0
                    }
610
0
                    else
611
0
                    {
612
0
                        char** bytes = g_strsplit(g_strstrip(column_text), " ", CANFD_MAX_DLEN);
613
0
                        int byte_i = 0;
614
0
                        while (byte_i < CANFD_MAX_DLEN) {
615
0
                            if (bytes[byte_i] == 0)
616
0
                                break;
617
0
                            peak_msg->data.data[byte_i] = (uint8_t)g_ascii_strtoull(bytes[byte_i], NULL, 16);
618
0
                            byte_i++;
619
0
                        }
620
0
                        g_strfreev(bytes);
621
0
                    }
622
0
                    break;
623
0
                }
624
0
                case Col_CanId:
625
0
                    peak_msg->id = (unsigned)g_ascii_strtoull(column_text, NULL, 16);
626
0
                    break;
627
0
                case Col_DLC_l:
628
0
                    peak_msg->data.length = (unsigned)g_ascii_strtoull(column_text, NULL, 10);
629
0
                    break;
630
0
                case Col_DLC_L:
631
0
                    peak_msg->data.length = dlc_to_data_length((unsigned)g_ascii_strtoull(column_text, NULL, 10));
632
0
                    break;
633
0
                case Col_TimeOffset:
634
0
                {
635
0
                    double tsd = g_ascii_strtod(column_text, NULL) / 1000.0; // parse to seconds
636
0
                    tsd += state->trace_start;
637
0
                    peak_msg->ts.secs = (uint64_t)tsd;
638
0
                    peak_msg->ts.nsecs = (int)((tsd - (uint64_t)tsd) * 1000000000);
639
0
                    break;
640
0
                }
641
0
                case Col_MessageType:
642
0
                    if (strcmp(column_text, "DT") == 0)
643
0
                    {
644
0
                        peak_msg->type = MSG_TYPE_STD;
645
0
                    }
646
0
                    else if (strcmp(column_text, "FD") == 0)
647
0
                    {
648
0
                        peak_msg->type = MSG_TYPE_STD_FD;
649
0
                    }
650
0
                    else if (strcmp(column_text, "FB") == 0)
651
0
                    {
652
                        // CAN FD data frame with BRS bit set (Bit Rate Switch).
653
0
                        peak_msg->type = MSG_TYPE_STD_FD; // TODO
654
0
                    }
655
0
                    else if (strcmp(column_text, "FE") == 0)
656
0
                    {
657
                        // CAN FD data frame with ESI bit set (Error State Indicator).
658
0
                        peak_msg->type = MSG_TYPE_ERR;
659
0
                    }
660
0
                    else if (strcmp(column_text, "BI") == 0)
661
0
                    {
662
                        // CAN FD data frame with both BRS and ESI bits set.
663
0
                        peak_msg->type = MSG_TYPE_ERR;
664
0
                    }
665
0
                    else if (strcmp(column_text, "RR") == 0)
666
0
                    {
667
0
                        peak_msg->type = MSG_TYPE_STD_RTR;
668
0
                    }
669
0
                    else if (strcmp(column_text, "ST") == 0)
670
0
                    {
671
                        // TODO: Hardware Status change.
672
                        // Currently not supported in Wireshark
673
0
                        g_free(column_text);
674
0
                        return false;
675
0
                    }
676
0
                    else if (strcmp(column_text, "ER") == 0)
677
0
                    {
678
0
                        peak_msg->type = MSG_TYPE_ERR;
679
0
                    }
680
0
                    else if (strcmp(column_text, "EC") == 0)
681
0
                    {
682
                        // TODO: Error Counter change
683
                        // Currently not supported in Wireshark
684
0
                        g_free(column_text);
685
0
                        return false;
686
0
                    }
687
0
                    else if (strcmp(column_text, "EV") == 0)
688
0
                    {
689
                        // Event. User-defined text, begins directly after bus specifier
690
                        // TODO add support for this event type
691
0
                        g_free(column_text);
692
0
                        return false;
693
0
                    }
694
0
                    break;
695
0
                case Col_MessageNumber:
696
0
                case Col_Reserved:
697
0
                case Col_Invalid:
698
0
                    break;
699
                /* Version 1 column types ignored */
700
0
                case Col_ErrorOrRtr_v1p0:
701
0
                case Col_BusNumber_v1:
702
0
                case Col_TimeOffset_v1_0:
703
0
                case Col_Data_v1_0:
704
0
                    break;
705
0
                }
706
0
                g_free(column_text);
707
0
            }
708
0
        }
709
0
        else
710
0
        {
711
            // non whitespace read
712
0
            if (last_char_is_ws)
713
0
            {
714
0
                for (int lut_i = 0; lut_i < Col_Invalid; lut_i++)
715
0
                {
716
0
                    if (state->column_positions[lut_i] == column_i)
717
0
                    {
718
0
                        current_column = lut_i;
719
0
                        break;
720
0
                    }
721
0
                }
722
0
                column_i++;
723
0
                column_start = i;
724
0
            }
725
0
        }
726
0
        last_char_is_ws = is_whitespace;
727
0
        i++;
728
0
    }
729
730
0
    return true;
731
0
}
732
733
static bool
734
peak_trc_read_packet(wtap *wth, FILE_T fh, peak_trc_state_t* state,
735
                     wtap_rec *rec, int *err, char **err_info,
736
                     int64_t *data_offset)
737
0
{
738
0
    wtap_can_msg_t peak_msg = {0};
739
0
    char line_buffer[PEAK_TRC_MAX_LINE_SIZE];
740
741
0
    while (!file_eof(fh))
742
0
    {
743
0
        if (data_offset)
744
0
            *data_offset = file_tell(fh);
745
746
        /* Read a line */
747
0
        if (file_gets(line_buffer, PEAK_TRC_MAX_LINE_SIZE, fh) == NULL)
748
0
        {
749
0
            *err = file_error(fh, err_info);
750
0
            return false;
751
0
        }
752
753
        /* Ignore empty or commented lines */
754
0
        if ((strlen(line_buffer) == 0) ||
755
0
            (line_buffer[0] == ';'))
756
0
            continue;
757
758
        //Start with unknown interface ID
759
0
        peak_msg.interface_id = WTAP_SOCKETCAN_INVALID_INTERFACE_ID;
760
761
0
        if (state->file_version_major >= 2)
762
0
        {
763
0
            if (!peak_trc_read_packet_v2(wth, state, &peak_msg, line_buffer))
764
0
                continue;
765
0
        }
766
0
        else
767
0
        {
768
0
            if (!peak_trc_read_packet_v1(wth, state, &peak_msg, line_buffer))
769
0
                continue;
770
0
        }
771
772
0
        if (peak_msg.id > 0x7FF)
773
0
        {
774
            //Translate from 11-bit to 32-bit IDs
775
0
            switch (peak_msg.type)
776
0
            {
777
0
            case MSG_TYPE_STD_FD:
778
0
                peak_msg.type = MSG_TYPE_EXT_FD;
779
0
                break;
780
0
            case MSG_TYPE_STD:
781
0
                peak_msg.type = MSG_TYPE_EXT;
782
0
                break;
783
0
            case MSG_TYPE_STD_RTR:
784
0
                peak_msg.type = MSG_TYPE_EXT_RTR;
785
0
                break;
786
0
            default:
787
                //Ignore other types
788
0
                break;
789
0
            }
790
0
        }
791
792
0
        return wtap_socketcan_gen_packet(wth, rec, &peak_msg, "peak", err, err_info);
793
0
    }
794
795
0
    return false;
796
0
}
797
798
static bool
799
peak_trc_read(wtap   *wth, wtap_rec *rec, int *err, char **err_info,
800
              int64_t *data_offset)
801
0
{
802
0
    peak_trc_state_t* state = (peak_trc_state_t*)wtap_socketcan_get_private_data(wth);
803
804
0
    ws_debug("%s: Try reading at offset %" PRIi64 "\n", G_STRFUNC, file_tell(wth->fh));
805
806
0
    return peak_trc_read_packet(wth, wth->fh, state, rec, err, err_info, data_offset);
807
0
}
808
809
static bool peak_trc_seek_read(wtap *wth, int64_t seek_off, wtap_rec *rec, int *err, char **err_info)
810
0
{
811
0
    ws_debug("%s: Read at offset %" PRIi64 "\n", G_STRFUNC, seek_off);
812
813
0
    peak_trc_state_t* state = (peak_trc_state_t*)wtap_socketcan_get_private_data(wth);
814
815
0
    if (file_seek(wth->random_fh, seek_off, SEEK_SET, err) == -1)
816
0
    {
817
0
        *err      = errno;
818
0
        *err_info = g_strdup(g_strerror(errno));
819
820
0
        return false;
821
0
    }
822
823
0
    return peak_trc_read_packet(wth, wth->random_fh, state, rec, err, err_info, NULL);
824
0
}
825
826
static void
827
clean_trc_state(peak_trc_state_t* state)
828
0
{
829
0
    if (state != NULL)
830
0
    {
831
0
        if (state->data_matcher != NULL)
832
0
            g_regex_unref(state->data_matcher);
833
834
0
        g_free(state);
835
0
    }
836
0
}
837
838
static void
839
peak_trc_close(void* tap_data)
840
0
{
841
0
    clean_trc_state((peak_trc_state_t*)tap_data);
842
0
}
843
844
wtap_open_return_val
845
peak_trc_open(wtap* wth, int* err, char** err_info)
846
0
{
847
0
    int64_t data_offset = 0;
848
0
    peak_trc_state_t* trc_state = g_new0(peak_trc_state_t, 1);
849
850
    /* wth->priv stores a pointer to the general file properties.
851
     * It it updated when the header data is parsed */
852
0
    wth->priv = trc_state;
853
854
0
    wtap_open_return_val open_val = peak_trc_parse(wth, trc_state, &data_offset, err, err_info);
855
0
    if (open_val != WTAP_OPEN_MINE)
856
0
    {
857
0
        clean_trc_state(trc_state);
858
0
        wth->priv = NULL;
859
0
        return open_val;
860
0
    }
861
862
0
    ws_debug("%s: This is our file\n", G_STRFUNC);
863
864
    /* Go to the start of the real packet data since header is now done */
865
0
    if (file_seek(wth->fh, data_offset, SEEK_SET, err) == -1)
866
0
    {
867
0
        *err = errno;
868
0
        *err_info = g_strdup(g_strerror(errno));
869
0
        return WTAP_OPEN_ERROR;
870
0
    }
871
872
0
    wtap_set_as_socketcan(wth, peak_trc_file_type_subtype, WTAP_TSPREC_USEC, trc_state, peak_trc_close);
873
0
    wth->subtype_read = peak_trc_read;
874
0
    wth->subtype_seek_read = peak_trc_seek_read;
875
876
0
    return WTAP_OPEN_MINE;
877
0
}
878
879
static const struct supported_block_type peak_trc_blocks_supported[] = {
880
    /*
881
     * We support packet blocks, with no comments or other options.
882
     */
883
    { WTAP_BLOCK_PACKET, MULTIPLE_BLOCKS_SUPPORTED, NO_OPTIONS_SUPPORTED }
884
};
885
886
static const struct file_type_subtype_info peak_trc_info = {
887
    "PEAK CAN TRC file", "peak-trc", "trc", NULL,
888
    false, BLOCKS_SUPPORTED(peak_trc_blocks_supported),
889
    NULL, NULL, NULL
890
};
891
892
void register_peak_trc(void)
893
16
{
894
16
    peak_trc_file_type_subtype = wtap_register_file_type_subtype(&peak_trc_info);
895
16
}
896
897
/*
898
 * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
899
 *
900
 * Local variables:
901
 * c-basic-offset: 4
902
 * tab-width: 8
903
 * indent-tabs-mode: nil
904
 * End:
905
 *
906
 * vi: set shiftwidth=4 tabstop=8 expandtab:
907
 * :indentSize=4:tabSize=8:noTabs=true:
908
 */