4 * Copyright (c) 1998 by Gilbert Ramirez <gram@alumni.rice.edu>
6 * SPDX-License-Identifier: GPL-2.0-or-later
10 * Reads log files from CLX000 CAN loggers from CSS Electronics:
12 * https://canlogger.csselectronics.com/clx000-docs/cl1000/log/index.html
13 * https://canlogger.csselectronics.com/clx000-docs/cl2000/log/index.html
15 * Based on the cCLLog.c, cCLLog.h, and wtap-cllog.c source files from
16 * the WS_v2.4-Plugin_v7.1.zip version of the CSS Electronics plugin at
18 * https://canlogger.csselectronics.com/downloads.php?q=wireshark
20 * with the files combined into one source file, modernized to
21 * fit into an up-to-date version of Wireshark, and cleaned up
22 * not to, for example, do seeks by rewinding and reading to
23 * get to the seek target.
25 * It could probably use some further cleanup.
37 #include <wsutil/str_util.h>
38 #include <wsutil/strtoi.h>
41 #include "file_wrappers.h"
43 /***********************************************************************************************************************
45 **********************************************************************************************************************/
46 #define MAX_LOG_LINE_FIELDS 7 /*( seqNo, timestamp, lost, SE, ID, length, data) */
47 /***********************************************************************************************************************
48 * Public type declarations
49 **********************************************************************************************************************/
50 /* Time stamp structure type (sec since start + ms resolution) */
51 typedef struct { time_t epoch
; uint16_t ms
; } cCLLog_timeStamp_t
;
56 msg_rx_standard_e
= 0,
57 msg_rx_extended_e
= 1,
58 msg_tx_standard_e
= 7,
59 msg_tx_extended_e
= 8,
60 } cCLLog_messageType_t
;
62 /* Typedef CAN-bus message type */
65 cCLLog_timeStamp_t timestamp
;
67 cCLLog_messageType_t msgType
;
74 typedef enum { silent_disabled_e
= 0, silent_enabled_e
} cCLLog_silentMode_t
;
77 typedef enum { cyclic_disabled_e
= 0, cyclic_enabled_e
} cCLLog_cyclicMode_t
;
80 typedef enum { type_CL1000_e
= 0, type_CL2000_e
, type_CL3000_e
} cCLLog_loggerType_t
;
82 typedef char * (*CLLog_gets_t
)(char *s
, int size
, void *stream
);
83 typedef int (*CLLog_rewind_t
)(void *stream
);
85 typedef struct cLLog_private cCLLog_logFileInfo_t
;
87 /* Type used to parse a field in a log line */
88 typedef bool (*parseFieldFunc_t
)(cCLLog_logFileInfo_t
*pInfo
, char *pField
, cCLLog_message_t
*pLogEntry
, int *err
, char **err_info
);
90 /* Log file information */
94 cCLLog_loggerType_t loggerType
;
100 cCLLog_timeStamp_t logStartTime
;
101 char logStartTimeString
[ 20 ];
105 char timeSeparatorMs
;
107 char dateAndTimeSeparator
;
109 cCLLog_silentMode_t silentMode
;
110 cCLLog_cyclicMode_t cyclicMode
;
112 parseFieldFunc_t parseFieldFunc
[ MAX_LOG_LINE_FIELDS
];
114 /* First log time stamp as relative offset */
115 cCLLog_timeStamp_t firstTimeStampAbs
;
118 /***********************************************************************************************************************
119 * Private definitions
120 **********************************************************************************************************************/
121 #define HEADER_LINE_PARSE_MAPPING_LENGTH array_length(headerLineParseMapping)
122 #define MAX_LOG_LINE_LENGTH 200
123 #define TIME_STAMP_STRING_MAX_LENGTH ( sizeof( "YYYY/MM/DDThh:mm:ss.kkk" ) )
124 #define TIME_STAMP_STRING_STRIPPED_MAX_LENGTH ( sizeof( "YYYYMMDDhhmmsskkk" ) )
126 /***********************************************************************************************************************
127 * Private type definitions
128 **********************************************************************************************************************/
129 /* Function type to parse a single log file line */
130 typedef bool (*parseFunc_t
)(cCLLog_logFileInfo_t
*pInfo
, char *pLine
, int *err
, char **err_info
);
132 /* Structure of the header parse mapping. A match string is paired with a parse function */
135 const char *pMatchString
;
136 parseFunc_t parseFunc
;
137 } headerLineParseMapping_t
;
139 /***********************************************************************************************************************
140 * Private function declarations
141 **********************************************************************************************************************/
142 static bool parseColumnHeaderFields( cCLLog_logFileInfo_t
*pInfo
, char *pColLine
);
143 static uint8_t stripTimeStamp( const cCLLog_logFileInfo_t
*pInfo
, char *pTimeStampString
);
145 /* Parse header lines functions */
146 static bool parseLogFileHeaderLine_type(cCLLog_logFileInfo_t
*pInfo
, char *pFieldValue
, int *err
, char **err_info
);
147 static bool parseLogFileHeaderLine_hwrev(cCLLog_logFileInfo_t
*pInfo
, char *pFieldValue
, int *err
, char **err_info
);
148 static bool parseLogFileHeaderLine_fwrev(cCLLog_logFileInfo_t
*pInfo
, char *pFieldValue
, int *err
, char **err_info
);
149 static bool parseLogFileHeaderLine_id(cCLLog_logFileInfo_t
*pInfo
, char *pFieldValue
, int *err
, char **err_info
);
150 static bool parseLogFileHeaderLine_sessionNo(cCLLog_logFileInfo_t
*pInfo
, char *pFieldValue
, int *err
, char **err_info
);
151 static bool parseLogFileHeaderLine_splitNo(cCLLog_logFileInfo_t
*pInfo
, char *pFieldValue
, int *err
, char **err_info
);
152 static bool parseLogFileHeaderLine_time(cCLLog_logFileInfo_t
*pInfo
, char *pFieldValue
, int *err
, char **err_info
);
153 static bool parseLogFileHeaderLine_valueSeparator(cCLLog_logFileInfo_t
*pInfo
, char *pFieldValue
, int *err
, char **err_info
);
154 static bool parseLogFileHeaderLine_timeFormat(cCLLog_logFileInfo_t
*pInfo
, char *pFieldValue
, int *err
, char **err_info
);
155 static bool parseLogFileHeaderLine_timeSeparator(cCLLog_logFileInfo_t
*pInfo
, char *pFieldValue
, int *err
, char **err_info
);
156 static bool parseLogFileHeaderLine_timeSeparatorMs(cCLLog_logFileInfo_t
*pInfo
, char *pFieldValue
, int *err
, char **err_info
);
157 static bool parseLogFileHeaderLine_dateSeparator(cCLLog_logFileInfo_t
*pInfo
, char *pFieldValue
, int *err
, char **err_info
);
158 static bool parseLogFileHeaderLine_timeAndDateSeparator(cCLLog_logFileInfo_t
*pInfo
, char *pFieldValue
, int *err
, char **err_info
);
159 static bool parseLogFileHeaderLine_bitRate(cCLLog_logFileInfo_t
*pInfo
, char *pFieldValue
, int *err
, char **err_info
);
160 static bool parseLogFileHeaderLine_silentMode(cCLLog_logFileInfo_t
*pInfo
, char *pFieldValue
, int *err
, char **err_info
);
161 static bool parseLogFileHeaderLine_cyclicMode(cCLLog_logFileInfo_t
*pInfo
, char *pFieldValue
, int *err
, char **err_info
);
162 /***********************************************************************************************************************
163 * Private variable definitions
164 **********************************************************************************************************************/
166 /* Array of header line match strings and associated parse functions */
167 static const headerLineParseMapping_t headerLineParseMapping
[] =
169 { .pMatchString
= "Logger type: ", .parseFunc
= parseLogFileHeaderLine_type
},
170 { .pMatchString
= "HW rev: ", .parseFunc
= parseLogFileHeaderLine_hwrev
},
171 { .pMatchString
= "FW rev: ", .parseFunc
= parseLogFileHeaderLine_fwrev
},
172 { .pMatchString
= "Logger ID: ", .parseFunc
= parseLogFileHeaderLine_id
},
173 { .pMatchString
= "Session No.: ", .parseFunc
= parseLogFileHeaderLine_sessionNo
},
174 { .pMatchString
= "Split No.: ", .parseFunc
= parseLogFileHeaderLine_splitNo
},
175 { .pMatchString
= "Time: ", .parseFunc
= parseLogFileHeaderLine_time
},
176 { .pMatchString
= "Value separator: ", .parseFunc
= parseLogFileHeaderLine_valueSeparator
},
177 { .pMatchString
= "Time format: ", .parseFunc
= parseLogFileHeaderLine_timeFormat
},
178 { .pMatchString
= "Time separator: ", .parseFunc
= parseLogFileHeaderLine_timeSeparator
},
179 { .pMatchString
= "Time separator ms: ", .parseFunc
= parseLogFileHeaderLine_timeSeparatorMs
},
180 { .pMatchString
= "Date separator: ", .parseFunc
= parseLogFileHeaderLine_dateSeparator
},
181 { .pMatchString
= "Time and date separator: ", .parseFunc
= parseLogFileHeaderLine_timeAndDateSeparator
},
182 { .pMatchString
= "Bit-rate: ", .parseFunc
= parseLogFileHeaderLine_bitRate
},
183 { .pMatchString
= "Silent mode: ", .parseFunc
= parseLogFileHeaderLine_silentMode
},
184 { .pMatchString
= "Cyclic mode: ", .parseFunc
= parseLogFileHeaderLine_cyclicMode
},
188 * Do a string copy to a buffer of a specified length.
189 * If the string will fit, return true.
190 * If the string won't fit, return false.
193 checked_strcpy(char *dest
, size_t destlen
, const char *src
)
197 srclen
= strlen(src
) + 1; // count the trailing '\0'
198 if (srclen
> destlen
)
200 memcpy(dest
, src
, srclen
);
204 /* TODO: Does not support separators set to numbers (will remove part of the time stamp also */
205 /* TODO: Does not support time stamps without ms, as given in the header */
206 /* TODO: Alot of copying slows down the parsing */
207 static bool parseFieldTS(cCLLog_logFileInfo_t
*pInfo
, char *pField
, cCLLog_message_t
*pLogEntry
, int *err
, char **err_info
)
212 /* Copy the string to not modify the original */
213 char timeStampCopy
[TIME_STAMP_STRING_MAX_LENGTH
];
214 if (!checked_strcpy(timeStampCopy
, sizeof timeStampCopy
, pField
))
216 *err
= WTAP_ERR_BAD_FILE
;
217 *err_info
= g_strdup("cllog: time stamp is too long");
221 /* Copy the header time stamp string to not modify the original */
222 char timeStampHeaderCopy
[TIME_STAMP_STRING_MAX_LENGTH
];
223 if (!checked_strcpy(timeStampHeaderCopy
, sizeof timeStampHeaderCopy
, pInfo
->logStartTimeString
))
225 *err
= WTAP_ERR_BAD_FILE
;
226 *err_info
= g_strdup("cllog: header time stamp too long");
230 /* Strip the delimiters from the time strings */
231 uint8_t msgTimeStrippedLen
= stripTimeStamp(pInfo
, timeStampCopy
);
232 if (msgTimeStrippedLen
> TIME_STAMP_STRING_STRIPPED_MAX_LENGTH
- 1) {
233 *err
= WTAP_ERR_BAD_FILE
;
234 *err_info
= g_strdup("cllog: time stamp incorrectly formatted");
238 uint8_t headerTimeStrippedLen
= stripTimeStamp(pInfo
, timeStampHeaderCopy
);
239 if (headerTimeStrippedLen
> TIME_STAMP_STRING_STRIPPED_MAX_LENGTH
- 1) {
240 *err
= WTAP_ERR_BAD_FILE
;
241 *err_info
= g_strdup("cllog: header time stamp incorrectly formatted");
245 /* Set time string (YYYYMMDDhhmmsskkk) to the epoch */
246 char timeStampStringFull
[TIME_STAMP_STRING_STRIPPED_MAX_LENGTH
] = "19700101000000000";
248 /* Copy the header time to the template */
249 memcpy(timeStampStringFull
, timeStampHeaderCopy
, headerTimeStrippedLen
);
251 /* Copy the stripped timestamp into the full template */
252 memcpy(&timeStampStringFull
[TIME_STAMP_STRING_STRIPPED_MAX_LENGTH
- 1 - msgTimeStrippedLen
], timeStampCopy
, msgTimeStrippedLen
);
253 timeStampStringFull
[TIME_STAMP_STRING_STRIPPED_MAX_LENGTH
- 1] = '\0';
255 memset(&tm
, 0, sizeof tm
);
257 /* YYYYMMDDThhmmss */
258 sscanf(timeStampStringFull
, "%4u%2u%2u%2u%2u%2u%3d",
270 /* To Epoch (mktime converts to epoch from local (!!!) timezone) */
271 pLogEntry
->timestamp
.epoch
= mktime(&tm
);
272 pLogEntry
->timestamp
.ms
= ms
;
274 /* Is first time stamp ? */
275 if (pInfo
->firstTimeStampAbs
.epoch
== 0 && pInfo
->firstTimeStampAbs
.ms
== 0)
277 pInfo
->firstTimeStampAbs
.epoch
= pLogEntry
->timestamp
.epoch
;
278 pInfo
->firstTimeStampAbs
.ms
= pLogEntry
->timestamp
.ms
;
284 static bool parseFieldLost(cCLLog_logFileInfo_t
*pInfo _U_
, char *pField
, cCLLog_message_t
*pLogEntry
, int *err
, char **err_info
)
288 if (!ws_strtou32(pField
, NULL
, &lost
)) {
289 *err
= WTAP_ERR_BAD_FILE
;
290 *err_info
= g_strdup_printf("cllog: lost packet count value is not valid");
293 pLogEntry
->lost
= lost
;
297 static bool parseFieldMsgType(cCLLog_logFileInfo_t
*pInfo _U_
, char *pField
, cCLLog_message_t
*pLogEntry
, int *err
, char **err_info
)
302 pLogEntry
->msgType
= msg_rx_standard_e
;
305 pLogEntry
->msgType
= msg_rx_extended_e
;
308 pLogEntry
->msgType
= msg_tx_standard_e
;
311 pLogEntry
->msgType
= msg_tx_extended_e
;
314 *err
= WTAP_ERR_BAD_FILE
;
315 *err_info
= g_strdup("cllog: unknown message type");
320 static bool parseFieldID(cCLLog_logFileInfo_t
*pInfo _U_
, char *pField
, cCLLog_message_t
*pLogEntry
, int *err
, char **err_info
)
324 if (!ws_hexstrtou32(pField
, NULL
, &id
)) {
325 *err
= WTAP_ERR_BAD_FILE
;
326 *err_info
= g_strdup_printf("cllog: ID value is not valid");
333 static bool parseFieldLength(cCLLog_logFileInfo_t
*pInfo _U_
, char *pField
, cCLLog_message_t
*pLogEntry
, int *err
, char **err_info
)
337 if (!ws_strtou32(pField
, NULL
, &length
)) {
338 *err
= WTAP_ERR_BAD_FILE
;
339 *err_info
= g_strdup_printf("cllog: length value is not valid");
342 if (length
> array_length(pLogEntry
->data
)) {
343 *err
= WTAP_ERR_BAD_FILE
;
344 *err_info
= g_strdup_printf("cllog: length value %u > maximum length %zu",
345 length
, array_length(pLogEntry
->data
));
348 pLogEntry
->length
= length
;
352 static bool parseFieldData(cCLLog_logFileInfo_t
*pInfo _U_
, char *pField
, cCLLog_message_t
*pLogEntry
, int *err
, char **err_info
)
354 char *pFieldStart
= pField
;
356 /* Set data length in case length field is not set explicitly in the log file */
357 pLogEntry
->length
= 0;
359 /* Loop all data bytes */
360 while (pLogEntry
->length
< array_length(pLogEntry
->data
))
365 if (*pFieldStart
== '\n' || *pFieldStart
== '\r' || *pFieldStart
== '\0')
370 hexdigit
= ws_xton(*pFieldStart
);
372 *err
= WTAP_ERR_BAD_FILE
;
373 *err_info
= g_strdup_printf("cllog: packet byte value 0x%02x is not valid", *pFieldStart
);
376 data
= (uint8_t)hexdigit
<< 4U;
378 hexdigit
= ws_xton(*pFieldStart
);
380 *err
= WTAP_ERR_BAD_FILE
;
381 *err_info
= g_strdup_printf("cllog: packet byte value 0x%02x is not valid", *pFieldStart
);
384 data
= data
| (uint8_t)hexdigit
;
386 pLogEntry
->data
[pLogEntry
->length
++] = data
;
391 static bool parseLogLine(cCLLog_logFileInfo_t
*pInfo
, char *pLine
, cCLLog_message_t
*pLogEntry
, int *err
, char **err_info
)
393 char *pFieldStart
= pLine
;
395 /* Loop all fields in log line */
396 for (unsigned int fieldNo
= 0, finalField
= 0; fieldNo
< MAX_LOG_LINE_FIELDS
&& finalField
== 0; fieldNo
++)
398 /* Find field end by separator */
399 char *pFieldEnd
= strchr(pFieldStart
, pInfo
->separator
);
401 /* If final field, then EOL marks the end of the field */
402 if (pFieldEnd
== NULL
)
404 pFieldEnd
= strchr(pFieldStart
, '\n');
408 /* Replace separator or terminator with string termination */
409 if (pFieldEnd
!= NULL
)
414 /* Is parse function assigned to field? */
415 if (pInfo
->parseFieldFunc
[fieldNo
] != NULL
)
418 if (!pInfo
->parseFieldFunc
[fieldNo
](pInfo
, pFieldStart
, pLogEntry
, err
, err_info
))
424 /* Skip over the separator */
425 pFieldStart
= pFieldEnd
+ 1;
430 /***********************************************************************************************************************
431 * parseColumnHeaderFields
433 * Parse the column fields and determine which fields are present and the position of the fields
435 * @param[ in ] pInfo Pointer to the CLLog object
436 * @param[ in ] pColLine The column line
437 **********************************************************************************************************************/
438 static bool parseColumnHeaderFields( cCLLog_logFileInfo_t
*pInfo
, char *pColLine
)
440 bool resultFlag
= false;
442 /* Initialise field start */
443 char *pFieldStart
= pColLine
;
445 /* Loop all fields in line */
446 for ( uint8_t fieldNo
= 0, finalField
= 0 ; fieldNo
< MAX_LOG_LINE_FIELDS
&& finalField
== 0 ; fieldNo
++ )
449 char *pFieldEnd
= strchr( pFieldStart
, pInfo
->separator
);
451 /* If final field, then EOL marks the end of the field */
452 if( pFieldEnd
== NULL
)
454 pFieldEnd
= strchr( pFieldStart
, '\n' );
458 /* Replace separator or terminator with string termination */
459 if (pFieldEnd
!= NULL
)
464 /* Set field number */
465 if( strcmp( pFieldStart
, "Timestamp" ) == 0 ) { pInfo
->parseFieldFunc
[ fieldNo
] = parseFieldTS
; resultFlag
= true; }
466 if( strcmp( pFieldStart
, "Lost" ) == 0 ) { pInfo
->parseFieldFunc
[ fieldNo
] = parseFieldLost
; resultFlag
= true; }
467 if( strcmp( pFieldStart
, "Type" ) == 0 ) { pInfo
->parseFieldFunc
[ fieldNo
] = parseFieldMsgType
; resultFlag
= true; }
468 if( strcmp( pFieldStart
, "ID" ) == 0 ) { pInfo
->parseFieldFunc
[ fieldNo
] = parseFieldID
; resultFlag
= true; }
469 if( strcmp( pFieldStart
, "Length" ) == 0 ) { pInfo
->parseFieldFunc
[ fieldNo
] = parseFieldLength
; resultFlag
= true; }
470 if( strcmp( pFieldStart
, "Data" ) == 0 ) { pInfo
->parseFieldFunc
[ fieldNo
] = parseFieldData
; resultFlag
= true; }
472 /* Set start of next field to end of previous + 1 */
473 pFieldStart
= pFieldEnd
+ 1;
479 /***********************************************************************************************************************
482 * Strips a time stamp string for any delimiters
483 **********************************************************************************************************************/
484 static uint8_t stripTimeStamp( const cCLLog_logFileInfo_t
*pInfo
, char *pTimeStampString
)
486 uint8_t strippedLength
= 0U;
488 /* Char by char, strip the delimiters from the time stamp string */
489 size_t timeStampStringLen
= strlen( pTimeStampString
);
490 for (size_t i
= 0U; i
< timeStampStringLen
; i
++ )
493 char charTmp
= pTimeStampString
[i
];
495 /* If delimiter, skip */
496 if( charTmp
== pInfo
->separator
){ continue; }
497 if( charTmp
== pInfo
->timeSeparator
){ continue; }
498 if( charTmp
== pInfo
->timeSeparatorMs
){ continue; }
499 if( charTmp
== pInfo
->dateSeparator
){ continue; }
500 if( charTmp
== pInfo
->dateAndTimeSeparator
){ continue; }
502 /* Not a delimiter, keep char */
503 pTimeStampString
[ strippedLength
++ ] = charTmp
;
505 pTimeStampString
[ strippedLength
] = '\0';
507 return strippedLength
;
510 static bool parseString(const char *pFieldValue
, char *valuep
, size_t valueSize
, char *fieldName
, int *err
, char **err_info
)
512 if (!checked_strcpy(valuep
, valueSize
, pFieldValue
))
514 *err
= WTAP_ERR_BAD_FILE
;
515 *err_info
= ws_strdup_printf("cllog: %s is too long",
522 static bool parseUnsigned(const char *pFieldValue
, uint32_t *valuep
, char *fieldName
, int *err
, char **err_info
)
526 if (!ws_strtou32(pFieldValue
, NULL
, &value
)) {
527 *err
= WTAP_ERR_BAD_FILE
;
528 *err_info
= ws_strdup_printf("cllog: %s value is not valid",
536 static bool parseSeparator(const char *pFieldValue
, char *separatorp
, char *fieldName
, int *err
, char **err_info
)
538 char separator
= '\0';
540 /* Separator field is if set e.g. ";" - that is 3 chars. Else it is "" */
541 if (strlen( pFieldValue
) == 3)
543 if (pFieldValue
[0] != '"' || !g_ascii_isprint(pFieldValue
[1]) ||
544 pFieldValue
[2] != '"')
546 *err
= WTAP_ERR_BAD_FILE
;
547 *err_info
= ws_strdup_printf("cllog: %s separator is not valid",
551 separator
= pFieldValue
[1];
553 *separatorp
= separator
;
557 static bool parseBoolean(const char *pFieldValue
, bool *value
, char *fieldName
, int *err
, char **err_info
)
559 if (strcmp(pFieldValue
, "true") == 0)
563 else if (strcmp(pFieldValue
, "false") == 0)
569 *err
= WTAP_ERR_BAD_FILE
;
570 *err_info
= ws_strdup_printf("cllog: %s value is not valid",
577 static bool parseLogFileHeaderLine_type(cCLLog_logFileInfo_t
*pInfo
, char *pFieldValue
, int *err
, char **err_info
)
579 if (strcmp(pFieldValue
, "CANLogger1000") == 0 || strcmp(pFieldValue
, "CL1000") == 0)
581 pInfo
->loggerType
= type_CL1000_e
;
583 else if (strcmp(pFieldValue
, "CANLogger2000") == 0 || strcmp(pFieldValue
, "CL2000") == 0)
585 pInfo
->loggerType
= type_CL2000_e
;
587 else if (strcmp(pFieldValue
, "CANLogger3000") == 0 || strcmp(pFieldValue
, "CL3000") == 0)
589 pInfo
->loggerType
= type_CL3000_e
;
593 *err
= WTAP_ERR_BAD_FILE
;
594 *err_info
= g_strdup("cllog: logger type value is not valid");
600 static bool parseLogFileHeaderLine_hwrev(cCLLog_logFileInfo_t
*pInfo
, char *pFieldValue
, int *err
, char **err_info
)
602 return parseString(pFieldValue
, pInfo
->hwrev
, sizeof pInfo
->hwrev
, "hardware revision", err
, err_info
);
605 static bool parseLogFileHeaderLine_fwrev(cCLLog_logFileInfo_t
*pInfo
, char *pFieldValue
, int *err
, char **err_info
)
607 return parseString(pFieldValue
, pInfo
->fwrev
, sizeof pInfo
->fwrev
, "firmware revision", err
, err_info
);
610 static bool parseLogFileHeaderLine_id(cCLLog_logFileInfo_t
*pInfo
, char *pFieldValue
, int *err
, char **err_info
)
612 return parseString(pFieldValue
, pInfo
->id
, sizeof pInfo
->id
, "ID", err
, err_info
);
615 static bool parseLogFileHeaderLine_sessionNo(cCLLog_logFileInfo_t
*pInfo
, char *pFieldValue
, int *err
, char **err_info
)
617 return parseUnsigned(pFieldValue
, &pInfo
->sessionNo
, "session number", err
, err_info
);
620 static bool parseLogFileHeaderLine_splitNo(cCLLog_logFileInfo_t
*pInfo
, char *pFieldValue
, int *err
, char **err_info
)
622 return parseUnsigned(pFieldValue
, &pInfo
->splitNo
, "split number", err
, err_info
);
625 static bool parseLogFileHeaderLine_time(cCLLog_logFileInfo_t
*pInfo
, char *pFieldValue
, int *err
, char **err_info
)
629 memset(&tm
, 0, sizeof tm
);
630 /* YYYYMMDDThhmmss */
632 "%4u%2u%2uT%2u%2u%2u",
642 /* To Epoch ( mktime converts to epoch from local (!!!) timezone )*/
643 pInfo
->logStartTime
.epoch
= mktime(&tm
);
644 pInfo
->logStartTime
.ms
= 0;
646 if (!checked_strcpy(pInfo
->logStartTimeString
, sizeof pInfo
->logStartTimeString
, pFieldValue
))
648 *err
= WTAP_ERR_BAD_FILE
;
649 *err_info
= g_strdup("cllog: time is too long");
655 static bool parseLogFileHeaderLine_valueSeparator(cCLLog_logFileInfo_t
*pInfo
, char *pFieldValue
, int *err
, char **err_info
)
657 return parseSeparator(pFieldValue
, &pInfo
->separator
, "value", err
, err_info
);
660 static bool parseLogFileHeaderLine_timeFormat(cCLLog_logFileInfo_t
*pInfo
, char *pFieldValue
, int *err
, char **err_info
)
664 if (!ws_strtou32(pFieldValue
, NULL
, &format
))
666 *err
= WTAP_ERR_BAD_FILE
;
667 *err_info
= g_strdup("cllog: time format value is not valid");
672 *err
= WTAP_ERR_BAD_FILE
;
673 *err_info
= g_strdup("cllog: time format value is not valid");
676 pInfo
->timeFormat
= (uint8_t)format
;
680 static bool parseLogFileHeaderLine_timeSeparator(cCLLog_logFileInfo_t
*pInfo
, char *pFieldValue
, int *err
, char **err_info
)
682 return parseSeparator(pFieldValue
, &pInfo
->timeSeparator
, "time", err
, err_info
);
685 static bool parseLogFileHeaderLine_timeSeparatorMs(cCLLog_logFileInfo_t
*pInfo
, char *pFieldValue
, int *err
, char **err_info
)
687 return parseSeparator(pFieldValue
, &pInfo
->timeSeparatorMs
, "time millisecond", err
, err_info
);
690 static bool parseLogFileHeaderLine_dateSeparator(cCLLog_logFileInfo_t
*pInfo
, char *pFieldValue
, int *err
, char **err_info
)
692 return parseSeparator(pFieldValue
, &pInfo
->dateSeparator
, "date", err
, err_info
);
695 static bool parseLogFileHeaderLine_timeAndDateSeparator(cCLLog_logFileInfo_t
*pInfo
, char *pFieldValue
, int *err
, char **err_info
)
697 return parseSeparator(pFieldValue
, &pInfo
->dateAndTimeSeparator
, "date and time", err
, err_info
);
700 static bool parseLogFileHeaderLine_bitRate(cCLLog_logFileInfo_t
*pInfo
, char *pFieldValue
, int *err
, char **err_info
)
702 return parseUnsigned(pFieldValue
, &pInfo
->bitRate
, "bit rate", err
, err_info
);
705 static bool parseLogFileHeaderLine_silentMode(cCLLog_logFileInfo_t
*pInfo
, char *pFieldValue
, int *err
, char **err_info
)
709 if (!parseBoolean(pFieldValue
, &silentMode
, "silent mode", err
, err_info
))
716 pInfo
->silentMode
= silent_enabled_e
;
720 pInfo
->silentMode
= silent_disabled_e
;
725 static bool parseLogFileHeaderLine_cyclicMode(cCLLog_logFileInfo_t
*pInfo
, char *pFieldValue
, int *err
, char **err_info
)
729 if (!parseBoolean(pFieldValue
, &cyclicMode
, "silent mode", err
, err_info
))
736 pInfo
->cyclicMode
= cyclic_enabled_e
;
740 pInfo
->cyclicMode
= cyclic_disabled_e
;
747 c:\development\wireshark\plugins\wimaxmacphy\cCLLog.c(248): warning C4
748 477: 'sscanf' : format string '%i' requires an argument of type 'int *',
749 but variadic argument 1 has type 'uint8_t *'
750 c:\development\wireshark\plugins\wimaxmacphy\cCLLog.c(274): warning C4
751 477: 'sscanf' : format string '%i' requires an argument of type 'int *',
752 but variadic argument 1 has type 'uint8_t *'
753 c:\development\wireshark\plugins\wimaxmacphy\cCLLog.c(288): warning C4
754 477: 'sscanf' : format string '%2x' requires an argument of type 'unsign
755 ed int *', but variadic argument 1 has type 'uint8_t *
762 static int cllog_file_type_subtype
= -1;
764 #define CAN_EFF_MASK 0x1FFFFFFF /* extended frame format (EFF) */
765 #define CAN_SFF_MASK 0x000007FF /* standard frame format (SFF) */
768 cllog_read_common(wtap
*wth
, FILE_T fh
, wtap_rec
*rec
, Buffer
*buf
, int *err
, char **err_info
)
770 cCLLog_logFileInfo_t
*clLog
= (cCLLog_logFileInfo_t
*) wth
->priv
;
771 char line
[MAX_LOG_LINE_LENGTH
];
772 cCLLog_message_t logEntry
;
776 if (file_gets(line
, sizeof(line
), fh
) == NULL
)
779 *err
= file_error(wth
->fh
, err_info
);
783 /* Default the log entry structure */
784 memset(&logEntry
, 0, sizeof(logEntry
));
787 if (!parseLogLine(clLog
, line
, &logEntry
, err
, err_info
))
792 rec
->rec_type
= REC_TYPE_PACKET
;
793 rec
->block
= wtap_block_create(WTAP_BLOCK_PACKET
);
794 rec
->presence_flags
= WTAP_HAS_TS
;
796 rec
->ts
.secs
= logEntry
.timestamp
.epoch
;
797 rec
->ts
.nsecs
= logEntry
.timestamp
.ms
* 1000U * 1000U;
799 rec
->rec_header
.packet_header
.caplen
= 8 + logEntry
.length
;
800 rec
->rec_header
.packet_header
.len
= 8 + logEntry
.length
;
802 if (logEntry
.msgType
== msg_tx_standard_e
|| logEntry
.msgType
== msg_tx_extended_e
)
804 wtap_block_add_uint32_option(rec
->block
, OPT_PKT_FLAGS
, PACK_FLAGS_DIRECTION_OUTBOUND
);
806 else if (logEntry
.msgType
== msg_rx_standard_e
|| logEntry
.msgType
== msg_rx_extended_e
)
808 wtap_block_add_uint32_option(rec
->block
, OPT_PKT_FLAGS
, PACK_FLAGS_DIRECTION_INBOUND
);
811 ws_buffer_assure_space(buf
, rec
->rec_header
.packet_header
.caplen
);
812 can_data
= ws_buffer_start_ptr(buf
);
814 can_data
[0] = (logEntry
.id
>> 24);
815 can_data
[1] = (logEntry
.id
>> 16);
816 can_data
[2] = (logEntry
.id
>> 8);
817 can_data
[3] = (logEntry
.id
>> 0);
818 can_data
[4] = logEntry
.length
;
823 if (logEntry
.msgType
== msg_tx_extended_e
|| logEntry
.msgType
== msg_rx_extended_e
|| (logEntry
.id
& CAN_EFF_MASK
) > CAN_SFF_MASK
)
826 memcpy(&can_data
[8], logEntry
.data
, logEntry
.length
);
831 cllog_read(wtap
*wth
, wtap_rec
*rec
, Buffer
*buf
, int *err
, char **err_info
, int64_t *data_offset
)
833 *data_offset
= file_tell(wth
->fh
);
835 return cllog_read_common(wth
, wth
->fh
, rec
, buf
, err
, err_info
);
839 cllog_seek_read(wtap
*wth
, int64_t seek_off
, wtap_rec
*rec
, Buffer
*buf
, int *err
, char **err_info
)
841 if (file_seek(wth
->random_fh
, seek_off
, SEEK_SET
, err
) == -1)
844 return cllog_read_common(wth
, wth
->random_fh
, rec
, buf
, err
, err_info
);
848 cllog_open(wtap
*wth
, int *err
, char **err_info
)
850 cCLLog_logFileInfo_t
*clLog
;
851 char line
[ MAX_LOG_LINE_LENGTH
];
854 clLog
= g_new0(cCLLog_logFileInfo_t
, 1);
856 /* Initialize the header information */
857 clLog
->loggerType
= 0;
858 clLog
->hwrev
[0] = '\0';
859 clLog
->fwrev
[0] = '\0';
861 clLog
->sessionNo
= 0;
863 clLog
->logStartTime
.epoch
= 0;
864 clLog
->logStartTime
.ms
= 0;
865 clLog
->logStartTimeString
[0] = '\0';
866 clLog
->separator
= '\0';
867 clLog
->timeFormat
= 0;
868 clLog
->timeSeparator
= '\0';
869 clLog
->timeSeparatorMs
= '\0';
870 clLog
->dateSeparator
= '\0';
871 clLog
->dateAndTimeSeparator
= '\0';
873 clLog
->silentMode
= 0;
874 clLog
->cyclicMode
= 0;
876 /* Set parse function pointers */
877 memset(clLog
->parseFieldFunc
, 0, sizeof( clLog
->parseFieldFunc
));
880 * We're at the beginning of the file. The header is a set
881 * of comment lines, each beginning with a '#'. Read each line,
882 * stopping if we see a non-comment line, and parse each
883 * comment line; if any aren't valid, quit and indicate that
884 * this isn't a CLX log file.
886 while ((linep
= file_gets(line
, sizeof(line
), wth
->fh
)) != NULL
&&
890 * Skip the comment character and white space following it.
893 while (*linep
== ' ' || *linep
== '\t')
899 * Skip over empty comment lines.
900 * XXX - should we treat that as an indication of an
907 * Look for the handler for this particular header line.
909 for (unsigned int i
= 0U; i
< HEADER_LINE_PARSE_MAPPING_LENGTH
; i
++)
911 const headerLineParseMapping_t
*pHeaderMapping
= &headerLineParseMapping
[i
];
912 size_t matchStringLen
= strlen(pHeaderMapping
->pMatchString
);
914 if (strncmp(linep
, pHeaderMapping
->pMatchString
, matchStringLen
) == 0 &&
915 pHeaderMapping
->parseFunc
!= NULL
)
918 * This matches this header value.
921 linep
+= matchStringLen
;
923 /* Replace any newline chars with end of line */
924 for (char *pChar
= linep
; ; pChar
++)
926 if (*pChar
== '\n' || *pChar
== '\r' || *pChar
== '\0')
936 if (!pHeaderMapping
->parseFunc(clLog
, linep
, err
, err_info
))
939 * XXX - should this file be rejected as not
940 * one of ours? Given the line looks like
941 * a comment that begins with a valid header
942 * field tag, it may be likely to be one of
946 if (*err
== WTAP_ERR_BAD_FILE
)
948 wmem_free(NULL
, *err_info
);
951 return WTAP_OPEN_NOT_MINE
;
958 * Did file_gets() fail?
963 * Yes - file_gets() didn't return a line.
964 * Did it get an error?
966 *err
= file_error(wth
->fh
, err_info
);
969 /* Yes. What was it? */
970 if (*err
== WTAP_ERR_SHORT_READ
)
972 /* Incomplete header, so not ours. */
974 return WTAP_OPEN_NOT_MINE
;
980 return WTAP_OPEN_ERROR
;
985 * No, it just got an EOF; treat it as our file, as
986 * older versions did so.
988 * XXX - should we treat it as not our file, as it lacks
989 * the column header line?
995 * We've read the first line after the header, so it's the column
996 * header line. Parse it.
998 if (!parseColumnHeaderFields(clLog
, linep
))
1001 return WTAP_OPEN_NOT_MINE
;
1007 wth
->file_type_subtype
= cllog_file_type_subtype
;
1008 wth
->file_encap
= WTAP_ENCAP_SOCKETCAN
;
1009 wth
->snapshot_length
= 0;
1011 wth
->subtype_read
= cllog_read
;
1012 wth
->subtype_seek_read
= cllog_seek_read
;
1013 wth
->file_tsprec
= WTAP_TSPREC_MSEC
;
1015 return WTAP_OPEN_MINE
;
1018 /* Options for packet blocks. */
1019 static const struct supported_option_type packet_block_options_supported
[] = {
1020 { OPT_PKT_FLAGS
, ONE_OPTION_SUPPORTED
},
1023 static const struct supported_block_type cllog_blocks_supported
[] = {
1025 * We support packet blocks, with only the flags option supported.
1027 { WTAP_BLOCK_PACKET
, MULTIPLE_BLOCKS_SUPPORTED
, OPTION_TYPES_SUPPORTED(packet_block_options_supported
) }
1030 static const struct file_type_subtype_info cllog_info
= {
1031 "CSS Electronics CLX000 CAN log", "cllog", "txt", NULL
,
1032 false, BLOCKS_SUPPORTED(cllog_blocks_supported
),
1037 register_canlogger(void)
1039 cllog_file_type_subtype
= wtap_register_file_type_subtype(&cllog_info
);