/* 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"
#define WS_LOG_DOMAIN "peak-trc"
#include <wireshark.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_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 {
    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 const 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 wtap_open_return_val
peak_trc_parse(wtap* wth, peak_trc_state_t* state, int64_t* offset, int* err, char** err_info)
{
    int64_t 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;

    ws_debug("%s: Trying peak_trc file decoder\n", G_STRFUNC);

    /* 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);

        ws_debug("%s: Starting parser at offset %" PRIi64 "\n", G_STRFUNC, seek_off);

        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 */
            *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;
                }

                GString* regex_str = g_string_new(NULL);
                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 (const 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. */

            char** 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="))
        {
            char** 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;
            }

            char** columns = g_strsplit(parts[1], ",", 0);
            int column_index = 0;
            for (char** 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 (const 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(wtap* wth, peak_trc_state_t* state, wtap_can_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;

        char* 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->type = MSG_TYPE_ERR;
            }
            break;
        case Col_CanId:
            peak_msg->id = (uint32_t)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->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->ts.secs = (uint64_t)tsd;
            peak_msg->ts.nsecs = (int)((tsd - (uint64_t)tsd) * 1000000000);
        }
        break;
        case Col_DLC:
            peak_msg->data.length = (uint8_t)g_ascii_strtoull(column_text, NULL, 10);
            break;
        case Col_BusNumber_v1:
        {
            char* interface_name = wmem_strdup_printf(NULL, "interface%s", column_text);
            peak_msg->interface_id = wtap_socketcan_find_or_create_new_interface(wth, interface_name);
            wmem_free(NULL, interface_name);
            break;
        }

        case Col_Data:
        case Col_Data_v1_0:
        {
            char* err_or_rtr = g_match_info_fetch(match_info, state->column_positions[Col_ErrorOrRtr_v1p0]);
            char** 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->type == MSG_TYPE_ERR))
            {
                peak_msg->type = MSG_TYPE_ERR;

                /* For LINUX_CAN_ERR the length has to be CAN_MAX_DLEN */
                peak_msg->data.length = CAN_MAX_DLEN;
                /* Clear the data of the message to populate it with SocketCAN meta data */
                memset(peak_msg->data.data, 0, CAN_MAX_DLEN);

                uint32_t old_id = peak_msg->id;
                peak_msg->id = 0;
                // ID: Type of Error Frame
                switch (old_id)
                {
                case 1: // 1 = Bit Error
                    peak_msg->id |= CAN_ERR_PROT_BIT;
                    break;
                case 2: // 2 = Form Error
                    peak_msg->id |= CAN_ERR_PROT_FORM;
                    break;
                case 4: // 4 = Stuff Error
                    peak_msg->id |= CAN_ERR_PROT_STUFF;
                    break;
                }

                //Translate PEAK error data into SocketCAN meta data for supported error types
                if ((peak_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->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->data.data[2] |= CAN_ERR_PROT_OVERLOAD;
                    }
                    else
                    {
                        // magically SocketCAN and PEAK error bit positions are identical with some exceptions
                        peak_msg->data.data[3] = bit_pos;
                        if (peak_msg->data.data[3] == 23)
                            peak_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->type == MSG_TYPE_STD_RTR))
            {
                peak_msg->type = MSG_TYPE_STD_RTR;
                peak_msg->data.length = 0;
            }
            else
            {
                peak_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->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 WS_DEBUG
    for (int i = 0; i < column_count; i++)
        ws_debug("%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(wtap* wth, peak_trc_state_t* state, wtap_can_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
                char* column_text = g_utf8_substring(line_buffer, column_start, i);

                ws_debug("Column %d: %s\n", current_column, column_text);

                switch (current_column) {
                case Col_BusNumber:
                {
                    char* interface_name = wmem_strdup_printf(NULL, "interface%s", column_text);
                    peak_msg->interface_id = wtap_socketcan_find_or_create_new_interface(wth, interface_name);
                    wmem_free(NULL, interface_name);
                    break;
                }
                case Col_Direction:
                    break;
                case Col_Data: {
                    if (peak_msg->type == MSG_TYPE_ERR)
                    {
                        // FIXME fill data and msg id
                    }
                    else
                    {
                        char** 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->data.data[byte_i] = (uint8_t)g_ascii_strtoull(bytes[byte_i], NULL, 16);
                            byte_i++;
                        }
                        g_strfreev(bytes);
                    }
                    break;
                }
                case Col_CanId:
                    peak_msg->id = (unsigned)g_ascii_strtoull(column_text, NULL, 16);
                    break;
                case Col_DLC:
                    peak_msg->data.length = (unsigned)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->ts.secs = (uint64_t)tsd;
                    peak_msg->ts.nsecs = (int)((tsd - (uint64_t)tsd) * 1000000000);
                    break;
                }
                case Col_MessageType:
                    if (strcmp(column_text, "DT") == 0)
                    {
                        peak_msg->type = MSG_TYPE_STD;
                    }
                    else if (strcmp(column_text, "FD") == 0)
                    {
                        peak_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->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->type = MSG_TYPE_ERR;
                    }
                    else if (strcmp(column_text, "BI") == 0)
                    {
                        // CAN FD data frame with both BRS and ESI bits set.
                        peak_msg->type = MSG_TYPE_ERR;
                    }
                    else if (strcmp(column_text, "RR") == 0)
                    {
                        peak_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->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, char **err_info,
                     int64_t *data_offset)
{
    wtap_can_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;

        //Start with unknown interface ID
        peak_msg.interface_id = WTAP_SOCKETCAN_INVALID_INTERFACE_ID;

        if (state->file_version_major >= 2)
        {
            if (!peak_trc_read_packet_v2(wth, state, &peak_msg, line_buffer))
                continue;
        }
        else
        {
            if (!peak_trc_read_packet_v1(wth, state, &peak_msg, line_buffer))
                continue;
        }

        if (peak_msg.id > 0x7FF)
        {
            //Translate from 11-bit to 32-bit IDs
            switch (peak_msg.type)
            {
            case MSG_TYPE_STD_FD:
                peak_msg.type = MSG_TYPE_EXT_FD;
                break;
            case MSG_TYPE_STD:
                peak_msg.type = MSG_TYPE_EXT;
                break;
            case MSG_TYPE_STD_RTR:
                peak_msg.type = MSG_TYPE_EXT_RTR;
                break;
            default:
                //Ignore other types
                break;
            }
        }

        return wtap_socketcan_gen_packet(wth, rec, &peak_msg, "peak", 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*)wtap_socketcan_get_private_data(wth);

    ws_debug("%s: Try reading at offset %" PRIi64 "\n", G_STRFUNC, file_tell(wth->fh));

    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)
{
    ws_debug("%s: Read at offset %" PRIi64 "\n", G_STRFUNC, seek_off);

    peak_trc_state_t* state = (peak_trc_state_t*)wtap_socketcan_get_private_data(wth);

    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
clean_trc_state(peak_trc_state_t* state)
{
    if (state != NULL)
    {
        if (state->data_matcher != NULL)
            g_regex_unref(state->data_matcher);

        g_free(state);
    }
}

static void
peak_trc_close(void* tap_data)
{
    clean_trc_state((peak_trc_state_t*)tap_data);
}

wtap_open_return_val
peak_trc_open(wtap* wth, int* err, char** err_info)
{
    int64_t data_offset = 0;
    peak_trc_state_t* trc_state = g_new0(peak_trc_state_t, 1);

    /* wth->priv stores a pointer to the general file properties.
     * It it updated when the header data is parsed */
    wth->priv = trc_state;

    wtap_open_return_val open_val = peak_trc_parse(wth, trc_state, &data_offset, err, err_info);
    if (open_val != WTAP_OPEN_MINE)
    {
        clean_trc_state(trc_state);
        wth->priv = NULL;
        return open_val;
    }

    ws_debug("%s: This is our file\n", G_STRFUNC);

    /* 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, trc_state, peak_trc_close);
    wth->subtype_read = peak_trc_read;
    wth->subtype_seek_read = peak_trc_seek_read;

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