Coverage Report

Created: 2026-11-12 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wireshark/wiretap/candump.c
Line
Count
Source
1
/* candump.c
2
 *
3
 * Wiretap Library
4
 * Copyright (c) 1998 by Gilbert Ramirez <gram@alumni.rice.edu>
5
 *
6
 * Support for candump log file format
7
 * Copyright (c) 2019 by Maksim Salau <maksim.salau@gmail.com>
8
 *
9
 * SPDX-License-Identifier: GPL-2.0-or-later
10
 */
11
12
#include <config.h>
13
#include "candump.h"
14
15
#include <file_wrappers.h>
16
#include <errno.h>
17
#include <wsutil/exported_pdu_tlvs.h>
18
#include <wsutil/strtoi.h>
19
#include <wsutil/str_util.h>
20
#include <wiretap/socketcan.h>
21
#include <epan/dissectors/packet-socketcan.h>
22
23
0
#define CANDUMP_MAX_LINE_SIZE 4096 // J1939 logs could contain long lines
24
25
static int candump_file_type_subtype = -1;
26
27
/*
28
 * Following 3 functions taken from gsmdecode-0.7bis, with permission:
29
 *
30
 *   https://web.archive.org/web/20091218112927/http://wiki.thc.org/gsm
31
 */
32
/*
33
* TODO: Find a better replacement for this
34
*/
35
static int
36
hex2bin(uint8_t* out, uint8_t* out_end, char* in)
37
0
{
38
0
    uint8_t* out_start = out;
39
0
    int is_low = 0;
40
0
    int c;
41
42
0
    while (*in != '\0')
43
0
    {
44
0
        c = ws_xton(*in);
45
0
        if (c < 0)
46
0
        {
47
0
            in++;
48
0
            continue;
49
0
        }
50
0
        if (out == out_end)
51
0
        {
52
            /* Too much data */
53
0
            return -1;
54
0
        }
55
0
        if (is_low == 0)
56
0
        {
57
0
            *out = c << 4;
58
0
            is_low = 1;
59
0
        }
60
0
        else {
61
0
            *out |= (c & 0x0f);
62
0
            is_low = 0;
63
0
            out++;
64
0
        }
65
0
        in++;
66
0
    }
67
68
0
    return (int)(out - out_start);
69
0
}
70
71
/*
72
 * This is written by the candump utility on Linux.
73
 */
74
75
static bool
76
candump_parse(wtap *wth, FILE_T fh, wtap_can_msg_t *msg, int64_t *offset,
77
              int *err, char **err_info)
78
0
{
79
0
    int64_t seek_off = 0;
80
0
    char line_buffer[CANDUMP_MAX_LINE_SIZE];
81
0
    char** tokens = NULL;
82
0
    char* data_start;
83
0
    bool ext_msg;
84
0
    int secs = 0,
85
0
        nsecs = 0;
86
87
    //Start with unknown interface ID
88
0
    msg->interface_id = WTAP_SOCKETCAN_INVALID_INTERFACE_ID;
89
90
0
    while(!file_eof(fh))
91
0
    {
92
93
0
        seek_off = file_tell(fh);
94
95
0
        if (file_gets(line_buffer, CANDUMP_MAX_LINE_SIZE, fh) == NULL)
96
0
        {
97
            /* Error reading file, bail out */
98
0
            *err = file_error(fh, err_info);
99
0
            return false;
100
0
        }
101
102
0
        tokens = g_strsplit(line_buffer, " ", 3);
103
104
0
        if (tokens[0] == NULL)
105
0
            break;
106
        /* Parse "(secs.nsecs)" timestamp format */
107
0
        {
108
0
            const char *p = tokens[0];
109
0
            const char *end;
110
0
            if (*p != '(')
111
0
                break;
112
0
            p++;
113
0
            if (!ws_strtoi(p, &end, &secs) || *end != '.')
114
0
                break;
115
0
            p = end + 1;
116
0
            if (!ws_strtoi(p, &end, &nsecs) || *end != ')')
117
0
                break;
118
0
        }
119
120
0
        msg->ts.secs = secs;
121
0
        msg->ts.nsecs = nsecs*1000;
122
123
0
        if (tokens[1] == NULL)
124
0
            break;
125
126
0
        if (tokens[2] == NULL)
127
0
            break;
128
0
        char* id_end = strstr(tokens[2], "#");
129
0
        if (id_end == NULL)
130
0
            break;
131
132
0
        if (!ws_hexstrtou32(tokens[2], (const char**)&id_end, &msg->id))
133
0
            break;
134
135
0
        ext_msg = (msg->id > CAN_SFF_MASK);
136
0
        msg->type = ext_msg ? MSG_TYPE_EXT : MSG_TYPE_STD;
137
138
        //Skip over the (first) #
139
0
        id_end++;
140
0
        data_start = id_end;
141
0
        bool valid = false;
142
0
        switch(*id_end)
143
0
        {
144
0
        case 0:
145
            //Packet with no data
146
0
            valid = true;
147
0
            break;
148
0
        case '#':
149
0
        {
150
0
            char strflags[2] = {0};
151
0
            char* flag_start = id_end + 1;
152
0
            if (!g_ascii_isxdigit(*flag_start))
153
0
                break;
154
155
0
            strflags[0] = *flag_start;
156
157
0
            if (!ws_hexstrtou8(strflags, NULL, &msg->flags))
158
0
                break;
159
0
            valid = true;
160
0
            msg->type = ext_msg ? MSG_TYPE_EXT_FD : MSG_TYPE_STD_FD;
161
162
            //Skip the flags
163
0
            data_start = id_end+2;
164
0
            break;
165
0
        }
166
0
        case 'R':
167
0
        {
168
0
            char rvalue = *(id_end + 1);
169
0
            if (g_ascii_isdigit(rvalue))
170
0
            {
171
0
                msg->data.length = rvalue - '0';
172
0
            }
173
0
            else
174
0
            {
175
0
                msg->data.length = 0;
176
0
            }
177
178
0
            msg->type = ext_msg ? MSG_TYPE_EXT_RTR : MSG_TYPE_STD_RTR;
179
180
            //No data
181
0
            data_start = NULL;
182
0
            valid = true;
183
0
            break;
184
0
        }
185
0
        default:
186
0
            if (!g_ascii_isxdigit(*id_end) && (!g_ascii_isspace(*id_end)))
187
0
            {
188
0
                g_strfreev(tokens);
189
0
                return false;
190
0
            }
191
0
            valid = true;
192
0
            break;
193
0
        }
194
195
0
        if (!valid)
196
0
            break;
197
198
        //Now grab the data
199
0
        if (data_start != NULL)
200
0
            msg->data.length = hex2bin(msg->data.data, &msg->data.data[CANFD_MAX_DLEN], data_start);
201
202
  /*
203
   * No errors - if we're reading a file of our own,
204
   * get the interface ID.
205
   *
206
         * The interface name is tokens[1].  Try to find the entry
207
         * with that name.
208
         */
209
0
        if (wth != NULL) {
210
0
            msg->interface_id = wtap_socketcan_find_or_create_new_interface(wth, tokens[1]);
211
0
        }
212
213
0
        g_strfreev(tokens);
214
215
0
        if (offset != NULL)
216
0
            *offset = seek_off;
217
218
0
        return true;
219
0
    }
220
221
0
    g_strfreev(tokens);
222
0
    return false;
223
0
}
224
225
static bool
226
candump_read(wtap *wth, wtap_rec *rec, int *err, char **err_info,
227
             int64_t *data_offset)
228
0
{
229
0
    wtap_can_msg_t msg = {0};
230
231
0
    ws_debug("%s: Try reading at offset %" PRIi64 "\n", G_STRFUNC, file_tell(wth->fh));
232
233
0
    if (!candump_parse(wth, wth->fh, &msg, data_offset, err, err_info))
234
0
        return false;
235
236
0
    ws_debug("%s: Stopped at offset %" PRIi64 "\n", G_STRFUNC, file_tell(wth->fh));
237
238
0
    return wtap_socketcan_gen_packet(wth, rec, &msg, "candump", err, err_info);
239
0
}
240
241
static bool
242
candump_seek_read(wtap *wth , int64_t seek_off, wtap_rec *rec,
243
                  int *err, char **err_info)
244
0
{
245
0
    wtap_can_msg_t msg = {0};
246
247
0
    ws_debug("%s: Read at offset %" PRIi64 "\n", G_STRFUNC, seek_off);
248
249
0
    if (file_seek(wth->random_fh, seek_off, SEEK_SET, err) == -1)
250
0
    {
251
0
        *err      = errno;
252
0
        *err_info = g_strdup(g_strerror(errno));
253
254
0
        return false;
255
0
    }
256
257
0
    if (!candump_parse(wth, wth->random_fh, &msg, NULL, err, err_info))
258
0
        return false;
259
260
0
    return wtap_socketcan_gen_packet(wth, rec, &msg, "candump", err, err_info);
261
0
}
262
263
wtap_open_return_val
264
candump_open(wtap* wth, int* err, char** err_info)
265
0
{
266
0
    wtap_can_msg_t temp_msg = {0};
267
268
    /*
269
     * We don't pass wth to candump_parse(), because we haven't yet
270
     * decided whether this is a candump file, and haven't set up
271
     * the hash table for interface names, and thus don't want it
272
     * trying to look up those names and adding new interfaces if
273
     * it doesn't find them; we do that in the read code.
274
     */
275
0
    if (!candump_parse(NULL, wth->fh, &temp_msg, NULL, err, err_info))
276
0
    {
277
0
        if (*err != 0 && *err != WTAP_ERR_SHORT_READ)
278
0
            return WTAP_OPEN_ERROR;
279
280
0
        *err = 0;
281
0
        *err_info = NULL;
282
0
        return WTAP_OPEN_NOT_MINE;
283
0
    }
284
285
0
    ws_debug("%s: This is our file\n", G_STRFUNC);
286
287
0
    if (file_seek(wth->fh, 0, SEEK_SET, err) == -1)
288
0
    {
289
0
        *err = errno;
290
0
        *err_info = g_strdup(g_strerror(errno));
291
292
0
        return WTAP_OPEN_ERROR;
293
0
    }
294
295
    /* This is a candump file (no private data needed) */
296
0
    wtap_set_as_socketcan(wth, candump_file_type_subtype, WTAP_TSPREC_USEC, NULL, NULL);
297
0
    wth->subtype_read = candump_read;
298
0
    wth->subtype_seek_read = candump_seek_read;
299
300
0
    return WTAP_OPEN_MINE;
301
0
}
302
303
static const struct supported_block_type candump_blocks_supported[] = {
304
    /*
305
     * We support packet blocks, with no comments or other options.
306
     */
307
    { WTAP_BLOCK_PACKET, MULTIPLE_BLOCKS_SUPPORTED, NO_OPTIONS_SUPPORTED }
308
};
309
310
static const struct file_type_subtype_info candump_info = {
311
    "Linux candump file", "candump", NULL, NULL,
312
    false, BLOCKS_SUPPORTED(candump_blocks_supported),
313
    NULL, NULL, NULL
314
};
315
316
void register_candump(void)
317
16
{
318
16
    candump_file_type_subtype = wtap_register_file_type_subtype(&candump_info);
319
16
}
320
321
/*
322
 * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
323
 *
324
 * Local variables:
325
 * c-basic-offset: 4
326
 * tab-width: 8
327
 * indent-tabs-mode: nil
328
 * End:
329
 *
330
 * vi: set shiftwidth=4 tabstop=8 expandtab:
331
 * :indentSize=4:tabSize=8:noTabs=true:
332
 */