/* busmaster_scanner.l
 *
 * Wiretap Library
 * Copyright (c) 1998 by Gilbert Ramirez <gram@alumni.rice.edu>
 *
 * Support for Busmaster log file format
 * Copyright (c) 2019 by Maksim Salau <maksim.salau@gmail.com>
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

%top {
/* Include this before everything else, for various large-file definitions */
#include "config.h"
}

%option noyywrap
%option noinput
%option nounput
%option batch
%option never-interactive
%option nodefault
%option prefix="busmaster_"
%option reentrant
%option extra-type="busmaster_state_t *"

%option noyy_scan_buffer
%option noyy_scan_bytes
%option noyy_scan_string

/*
 * We have to override the memory allocators so that we don't get
 * "unused argument" warnings from the yyscanner argument (which
 * we don't use, as we have a global memory allocator).
 *
 * We provide, as macros, our own versions of the routines generated by Flex,
 * which just call malloc()/realloc()/free() (as the Flex versions do),
 * discarding the extra argument.
 */
%option noyyalloc
%option noyyrealloc
%option noyyfree

%{

#include <ws_diag_control.h>
#include <wiretap/file_wrappers.h>
#include "busmaster_parser.h"
#include "busmaster_priv.h"

#ifndef HAVE_UNISTD_H
#define YY_NO_UNISTD_H
#endif

static int busmaster_yyinput(void *buf, unsigned int length, busmaster_state_t *state)
{
    int ret = file_read(buf, length, state->fh);

    if (ret < 0)
    {
        state->err = file_error(state->fh, &state->err_info);
        return YY_NULL;
    }

    return ret;
}

#define YY_INPUT(buf, result, max_size) \
    do { (result) = busmaster_yyinput((buf), (max_size), yyextra); } while (0)

/* Count bytes read. This is required in order to rewind the file
 * to the beginning of the next packet, since flex reads more bytes
 * before executing the action that does yyterminate(). */
#define YY_USER_ACTION do { yyextra->file_bytes_read += yyleng; } while (0);

/*
 * Sleazy hack to suppress compiler warnings in yy_fatal_error().
 */
#define YY_EXIT_FAILURE ((void)yyscanner, 2)

/*
 * Macros for the allocators, to discard the extra argument.
 */
#define busmaster_alloc(size, yyscanner)          (void *)malloc(size)
#define busmaster_realloc(ptr, size, yyscanner)   (void *)realloc((char *)(ptr), (size))
#define busmaster_free(ptr, yyscanner)            free((char *)(ptr))

DIAG_OFF_FLEX

%}

SPC [ \t]+
ENDL [\r\n][ \t\r\n]*
INT [0-9]+
NUM (0x)?[0-9A-Fa-f]+

%x HEADER TIME
%x HEADER_CHANNELS HEADER_DB_FILES

%%

<*>{SPC}   ;
<INITIAL>{ENDL}               { yyterminate(); };
<HEADER,TIME>{ENDL}           { YY_FATAL_ERROR("Unterminated header statement"); }

"***"                         { BEGIN(HEADER); }
<HEADER,TIME>"***"{ENDL}"***" { BEGIN(HEADER); return TOKEN_ENDL; }
<HEADER>"***"{ENDL}           { BEGIN(INITIAL); yyterminate(); }
<HEADER>"BUSMASTER"           { return TOKEN_HEADER_VER; }
<HEADER>"PROTOCOL CAN"        { yyextra->token.v0 = PROTOCOL_CAN;   return TOKEN_PROTOCOL_TYPE; }
<HEADER>"PROTOCOL J1939"      { yyextra->token.v0 = PROTOCOL_J1939; return TOKEN_PROTOCOL_TYPE; }
<HEADER>"PROTOCOL LIN"        { yyextra->token.v0 = PROTOCOL_LIN;   return TOKEN_PROTOCOL_TYPE; }
<HEADER>"START DATE AND TIME" { BEGIN(TIME); return TOKEN_START_TIME; }
<HEADER>"END DATE AND TIME"   { BEGIN(TIME); return TOKEN_END_TIME; }
<HEADER>"DEC"                 { yyextra->token.v0 = DATA_MODE_DEC; return TOKEN_DATA_MODE; }
<HEADER>"HEX"                 { yyextra->token.v0 = DATA_MODE_HEX; return TOKEN_DATA_MODE; }
<HEADER>"ABSOLUTE MODE"       { yyextra->token.v0 = TIME_MODE_ABSOLUTE; return TOKEN_TIME_MODE; }
<HEADER>"SYSTEM MODE"         { yyextra->token.v0 = TIME_MODE_SYSTEM;   return TOKEN_TIME_MODE; }
<HEADER>"RELATIVE MODE"       { yyextra->token.v0 = TIME_MODE_RELATIVE; return TOKEN_TIME_MODE; }
<HEADER>"[START LOGGING SESSION]" { return TOKEN_START_SESSION; }
<HEADER>"[STOP LOGGING SESSION]" { return TOKEN_STOP_SESSION; }
<HEADER>"START CHANNEL BAUD RATE***" { BEGIN(HEADER_CHANNELS); }
<HEADER>"START DATABASE FILES (DBF/DBC)***" { BEGIN(HEADER_DB_FILES); }
<HEADER>.                     { return TOKEN_HEADER_CHAR; }

<HEADER_CHANNELS>"***END CHANNEL BAUD RATE***"{ENDL}"***" { BEGIN(HEADER); }
<HEADER_CHANNELS>.+           ;
<HEADER_CHANNELS>{ENDL}       ;

<HEADER_DB_FILES>"***END DATABASE FILES (DBF/DBC)***"{ENDL}"***" { BEGIN(HEADER); }
<HEADER_DB_FILES>"***END OF DATABASE FILES (DBF/DBC)***"{ENDL}"***" { BEGIN(HEADER); }
<HEADER_DB_FILES>.+           ;
<HEADER_DB_FILES>{ENDL}       ;

<TIME>{INT} { yyextra->token.v0 = strtoul(yytext, NULL, 10); return TOKEN_INT; }
<TIME>:     { return TOKEN_COLON; }
<TIME>.     { return TOKEN_INVALID_CHAR; }

<INITIAL>{INT}:{INT}:{INT}:{INT} {
                    char *endp;
                    char *strp;

                    yyextra->token.v0 = strtoul(yytext, &endp, 10);
                    if (*endp != ':' || endp == yytext)
                        return TOKEN_INVALID_NUMBER;

                    strp = endp + 1;
                    yyextra->token.v1 = strtoul(strp, &endp, 10);
                    if (*endp != ':' || endp == strp)
                        return TOKEN_INVALID_NUMBER;

                    strp = endp + 1;
                    yyextra->token.v2 = strtoul(strp, &endp, 10);
                    if (*endp != ':' || endp == strp)
                        return TOKEN_INVALID_NUMBER;

                    strp = endp + 1;
                    yyextra->token.v3 = strtoul(strp, &endp, 10);
                    if (*endp != '\0' || endp == strp)
                        return TOKEN_INVALID_NUMBER;

                    return TOKEN_MSG_TIME;
                }

<INITIAL>{NUM}  {
                    char *endp;

                    if (yyextra->header.data_mode == DATA_MODE_HEX)
                        yyextra->token.v0 = strtoul(yytext, &endp, 16);
                    else if (yyextra->header.data_mode == DATA_MODE_DEC)
                        yyextra->token.v0 = strtoul(yytext, &endp, 10);
                    else
                        return TOKEN_INVALID_NUMBER;

                    if (*endp != '\0' || endp == yytext)
                        return TOKEN_INVALID_NUMBER;

                    return TOKEN_INT;
                }

<INITIAL>[RT]x  { return TOKEN_MSG_DIR; }
<INITIAL>s      { yyextra->token.v0 = MSG_TYPE_STD;     return TOKEN_MSG_TYPE; }
<INITIAL>sr     { yyextra->token.v0 = MSG_TYPE_STD_RTR; return TOKEN_MSG_TYPE; }
<INITIAL>x      { yyextra->token.v0 = MSG_TYPE_EXT;     return TOKEN_MSG_TYPE; }
<INITIAL>xr     { yyextra->token.v0 = MSG_TYPE_EXT_RTR; return TOKEN_MSG_TYPE; }
<INITIAL>s-fd   { yyextra->token.v0 = MSG_TYPE_STD_FD;  return TOKEN_MSG_TYPE; }
<INITIAL>x-fd   { yyextra->token.v0 = MSG_TYPE_EXT_FD;  return TOKEN_MSG_TYPE; }
<INITIAL>ERR.*  { yyextra->token.v0 = MSG_TYPE_ERR;     return TOKEN_ERR_MSG_TYPE; }

<INITIAL>("NONE"|"CMD"|"RQST"|"DATA"|"BROADCAST"|"ACK"|"GRP_FUNC"|"ACL"|"RQST_ACL"|"CA"|"BAM"|"RTS"|"CTS"|"EOM"|"CON_ABORT"|"TPDT") {
    return TOKEN_J1939_MSG_TYPE;
}

<INITIAL>.      { return TOKEN_INVALID_CHAR; }

%%

DIAG_ON_FLEX
