/src/wireshark/wiretap/busmaster.c
Line | Count | Source |
1 | | /* busmaster.c |
2 | | * |
3 | | * Wiretap Library |
4 | | * Copyright (c) 1998 by Gilbert Ramirez <gram@alumni.rice.edu> |
5 | | * |
6 | | * Support for Busmaster log file format |
7 | | * Copyright (c) 2019 by Maksim Salau <maksim.salau@gmail.com> |
8 | | * |
9 | | * See https://rbei-etas.github.io/busmaster/ for the BUSMASTER software. |
10 | | * |
11 | | * SPDX-License-Identifier: GPL-2.0-or-later |
12 | | */ |
13 | | |
14 | 0 | #define WS_LOG_DOMAIN "busmaster" |
15 | | #include "config.h" |
16 | | #include <wireshark.h> |
17 | | #include "busmaster.h" |
18 | | |
19 | | #include <socketcan.h> |
20 | | #include <file_wrappers.h> |
21 | | #include <epan/dissectors/packet-socketcan.h> |
22 | | #include <wsutil/exported_pdu_tlvs.h> |
23 | | #include "busmaster_priv.h" |
24 | | #include <inttypes.h> |
25 | | #include <string.h> |
26 | | #include <errno.h> |
27 | | |
28 | | //Private data for the wiretap |
29 | | typedef struct { |
30 | | GSList* list; //List of private entries |
31 | | } busmaster_data_t; |
32 | | |
33 | | static void |
34 | | busmaster_close(void* tap_data); |
35 | | |
36 | | static bool |
37 | | busmaster_read(wtap *wth, wtap_rec *rec, |
38 | | int *err, char **err_info, |
39 | | int64_t *data_offset); |
40 | | |
41 | | static bool |
42 | | busmaster_seek_read(wtap *wth, int64_t seek_off, |
43 | | wtap_rec *rec, |
44 | | int *err, char **err_info); |
45 | | |
46 | | static int busmaster_file_type_subtype = -1; |
47 | | |
48 | | void register_busmaster(void); |
49 | | |
50 | | |
51 | | static bool |
52 | | busmaster_gen_packet(wtap* wth, |
53 | | wtap_rec* rec, |
54 | | const busmaster_priv_t* priv_entry, const msg_t* msg, |
55 | | int* err, char** err_info) |
56 | 0 | { |
57 | 0 | wtap_can_msg_t can_msg; |
58 | 0 | bool has_ts = false; |
59 | |
|
60 | 0 | can_msg.id = msg->id; |
61 | 0 | can_msg.type = msg->type; |
62 | 0 | can_msg.flags = 0; //Not used by BUSMASTER |
63 | 0 | can_msg.interface_id = WTAP_SOCKETCAN_INVALID_INTERFACE_ID; //Not (yet) used by BUSMASTER |
64 | 0 | can_msg.data = msg->data; |
65 | | |
66 | | //Need to convert the timestamp |
67 | 0 | if (priv_entry->time_mode == TIME_MODE_SYSTEM) |
68 | 0 | { |
69 | 0 | struct tm tm; |
70 | |
|
71 | 0 | tm.tm_year = priv_entry->start.d.year - 1900; |
72 | 0 | tm.tm_mon = priv_entry->start.d.month - 1; |
73 | 0 | tm.tm_mday = priv_entry->start.d.day; |
74 | 0 | tm.tm_hour = msg->timestamp.hours; |
75 | 0 | tm.tm_min = msg->timestamp.minutes; |
76 | 0 | tm.tm_sec = msg->timestamp.seconds; |
77 | 0 | tm.tm_isdst = -1; |
78 | |
|
79 | 0 | can_msg.ts.secs = mktime(&tm); |
80 | 0 | can_msg.ts.nsecs = msg->timestamp.micros * 1000u; |
81 | 0 | has_ts = true; |
82 | 0 | } |
83 | 0 | else if (priv_entry->time_mode == TIME_MODE_ABSOLUTE) |
84 | 0 | { |
85 | 0 | struct tm tm; |
86 | 0 | uint32_t micros; |
87 | |
|
88 | 0 | tm.tm_year = priv_entry->start.d.year - 1900; |
89 | 0 | tm.tm_mon = priv_entry->start.d.month - 1; |
90 | 0 | tm.tm_mday = priv_entry->start.d.day; |
91 | 0 | tm.tm_hour = priv_entry->start.t.hours; |
92 | 0 | tm.tm_min = priv_entry->start.t.minutes; |
93 | 0 | tm.tm_sec = priv_entry->start.t.seconds; |
94 | 0 | tm.tm_isdst = -1; |
95 | |
|
96 | 0 | can_msg.ts.secs = mktime(&tm); |
97 | |
|
98 | 0 | can_msg.ts.secs += msg->timestamp.hours * 3600; |
99 | 0 | can_msg.ts.secs += msg->timestamp.minutes * 60; |
100 | 0 | can_msg.ts.secs += msg->timestamp.seconds; |
101 | |
|
102 | 0 | micros = priv_entry->start.t.micros + msg->timestamp.micros; |
103 | 0 | if (micros >= 1000000u) |
104 | 0 | { |
105 | 0 | micros -= 1000000u; |
106 | 0 | can_msg.ts.secs += 1; |
107 | 0 | } |
108 | |
|
109 | 0 | can_msg.ts.nsecs = micros * 1000u; |
110 | 0 | has_ts = true; |
111 | 0 | } |
112 | 0 | else |
113 | 0 | { |
114 | 0 | can_msg.ts.secs = 0; |
115 | 0 | can_msg.ts.nsecs = 0; |
116 | 0 | } |
117 | |
|
118 | 0 | if (!wtap_socketcan_gen_packet(wth, rec, &can_msg, "busmaster", err, err_info)) |
119 | 0 | return false; |
120 | | |
121 | | //Packet may not have timestamp |
122 | 0 | if (!has_ts) |
123 | 0 | rec->presence_flags &= (~WTAP_HAS_TS); |
124 | 0 | return true; |
125 | 0 | } |
126 | | |
127 | | static log_entry_type_t |
128 | | busmaster_parse(FILE_T fh, busmaster_state_t *state, int *err, char **err_info) |
129 | 0 | { |
130 | 0 | bool ok; |
131 | 0 | int64_t seek_off; |
132 | |
|
133 | 0 | ws_debug("%s: Running busmaster file decoder\n", G_STRFUNC); |
134 | |
|
135 | 0 | state->fh = fh; |
136 | |
|
137 | 0 | do |
138 | 0 | { |
139 | 0 | if (file_eof(fh)) |
140 | 0 | return LOG_ENTRY_EOF; |
141 | | |
142 | 0 | seek_off = file_tell(fh); |
143 | 0 | ws_debug("%s: Starting parser at offset %" PRIi64 "\n", |
144 | 0 | G_STRFUNC, seek_off); |
145 | 0 | state->file_bytes_read = 0; |
146 | 0 | ok = run_busmaster_parser(state, err, err_info); |
147 | | |
148 | | /* Rewind the file to the offset we have finished parsing */ |
149 | 0 | ws_debug("%s: Rewinding to offset %" PRIi64 "\n", |
150 | 0 | G_STRFUNC, seek_off + state->file_bytes_read); |
151 | 0 | if (file_seek(fh, seek_off + state->file_bytes_read, SEEK_SET, err) == -1) |
152 | 0 | { |
153 | 0 | g_free(*err_info); |
154 | 0 | *err = errno; |
155 | 0 | *err_info = g_strdup(g_strerror(errno)); |
156 | 0 | return LOG_ENTRY_ERROR; |
157 | 0 | } |
158 | 0 | } |
159 | 0 | while (ok && state->entry_type == LOG_ENTRY_NONE); |
160 | | |
161 | 0 | if (!ok) |
162 | 0 | return LOG_ENTRY_ERROR; |
163 | | |
164 | 0 | ws_debug("%s: Success\n", G_STRFUNC); |
165 | |
|
166 | 0 | return state->entry_type; |
167 | 0 | } |
168 | | |
169 | | wtap_open_return_val |
170 | | busmaster_open(wtap *wth, int *err, char **err_info) |
171 | 0 | { |
172 | 0 | busmaster_state_t state = {0}; |
173 | 0 | log_entry_type_t entry; |
174 | |
|
175 | 0 | ws_debug("%s: Trying to open with busmaster log reader\n", |
176 | 0 | G_STRFUNC); |
177 | | |
178 | | /* Rewind to the beginning */ |
179 | 0 | if (file_seek(wth->fh, 0, SEEK_SET, err) == -1) |
180 | 0 | return WTAP_OPEN_ERROR; |
181 | | |
182 | 0 | entry = busmaster_parse(wth->fh, &state, err, err_info); |
183 | |
|
184 | 0 | g_free(*err_info); |
185 | 0 | *err_info = NULL; |
186 | 0 | *err = 0; |
187 | |
|
188 | 0 | if (entry != LOG_ENTRY_HEADER) |
189 | 0 | return WTAP_OPEN_NOT_MINE; |
190 | | |
191 | | /* Rewind to the beginning, so busmaster_read may read from the very beginning */ |
192 | 0 | if (file_seek(wth->fh, 0, SEEK_SET, err) == -1) |
193 | 0 | return WTAP_OPEN_ERROR; |
194 | | |
195 | 0 | ws_debug("%s: That's a busmaster log\n", G_STRFUNC); |
196 | |
|
197 | 0 | wth->subtype_read = busmaster_read; |
198 | 0 | wth->subtype_seek_read = busmaster_seek_read; |
199 | 0 | wtap_set_as_socketcan(wth, busmaster_file_type_subtype, WTAP_TSPREC_USEC, g_new0(busmaster_data_t, 1), busmaster_close); |
200 | |
|
201 | 0 | return WTAP_OPEN_MINE; |
202 | 0 | } |
203 | | |
204 | | static void |
205 | | busmaster_close(void* tap_data) |
206 | 0 | { |
207 | 0 | busmaster_data_t* data = (busmaster_data_t*)tap_data; |
208 | |
|
209 | 0 | ws_debug("%s\n", G_STRFUNC); |
210 | |
|
211 | 0 | g_slist_free_full(data->list, g_free); |
212 | 0 | g_free(data); |
213 | 0 | } |
214 | | |
215 | | static busmaster_priv_t * |
216 | | busmaster_find_priv_entry(GSList* priv_list, int64_t offset) |
217 | 0 | { |
218 | 0 | GSList *list; |
219 | |
|
220 | 0 | for (list = priv_list; list; list = g_slist_next(list)) |
221 | 0 | { |
222 | 0 | busmaster_priv_t *entry = (busmaster_priv_t *)list->data; |
223 | |
|
224 | 0 | if (((entry->file_end_offset == -1) |
225 | 0 | && (g_slist_next(list) == NULL)) |
226 | 0 | || ((offset >= entry->file_start_offset) |
227 | 0 | && (offset <= entry->file_end_offset))) |
228 | 0 | { |
229 | 0 | return entry; |
230 | 0 | } |
231 | 0 | } |
232 | | |
233 | 0 | return NULL; |
234 | 0 | } |
235 | | |
236 | | static bool |
237 | | busmaster_read(wtap *wth, wtap_rec *rec, int *err, char **err_info, |
238 | | int64_t *data_offset) |
239 | 0 | { |
240 | 0 | log_entry_type_t entry; |
241 | 0 | busmaster_state_t state; |
242 | 0 | busmaster_priv_t *priv_entry; |
243 | 0 | bool is_msg = false; |
244 | 0 | bool is_ok = true; |
245 | 0 | busmaster_data_t* priv_data = (busmaster_data_t*)wtap_socketcan_get_private_data(wth); |
246 | |
|
247 | 0 | while (!is_msg && is_ok) |
248 | 0 | { |
249 | 0 | ws_debug("%s: offset = %" PRIi64 "\n", |
250 | 0 | G_STRFUNC, file_tell(wth->fh)); |
251 | |
|
252 | 0 | if (file_eof(wth->fh)) |
253 | 0 | { |
254 | 0 | ws_debug("%s: End of file detected, nothing to do here\n", |
255 | 0 | G_STRFUNC); |
256 | 0 | *err = 0; |
257 | 0 | *err_info = NULL; |
258 | 0 | return false; |
259 | 0 | } |
260 | | |
261 | 0 | *data_offset = file_tell(wth->fh); |
262 | 0 | priv_entry = busmaster_find_priv_entry(priv_data->list, *data_offset); |
263 | |
|
264 | 0 | memset(&state, 0, sizeof(state)); |
265 | 0 | if (priv_entry) |
266 | 0 | state.header = *priv_entry; |
267 | 0 | entry = busmaster_parse(wth->fh, &state, err, err_info); |
268 | |
|
269 | 0 | ws_debug("%s: analyzing output\n", G_STRFUNC); |
270 | 0 | switch (entry) |
271 | 0 | { |
272 | 0 | case LOG_ENTRY_EMPTY: |
273 | 0 | break; |
274 | 0 | case LOG_ENTRY_FOOTER_AND_HEADER: |
275 | 0 | case LOG_ENTRY_FOOTER: |
276 | 0 | priv_entry = (busmaster_priv_t *)g_slist_last(priv_data->list)->data; |
277 | 0 | if (!priv_entry) |
278 | 0 | { |
279 | 0 | *err = WTAP_ERR_BAD_FILE; |
280 | 0 | *err_info = g_strdup("Header is missing"); |
281 | 0 | return false; |
282 | 0 | } |
283 | 0 | priv_entry->file_end_offset = *data_offset; |
284 | 0 | if (entry == LOG_ENTRY_FOOTER) |
285 | 0 | break; |
286 | | /* fall-through */ |
287 | 0 | case LOG_ENTRY_HEADER: |
288 | 0 | if (state.header.protocol != PROTOCOL_CAN && |
289 | 0 | state.header.protocol != PROTOCOL_J1939) |
290 | 0 | { |
291 | 0 | *err = WTAP_ERR_UNSUPPORTED; |
292 | 0 | *err_info = g_strdup("Unsupported protocol type"); |
293 | 0 | return false; |
294 | 0 | } |
295 | | |
296 | 0 | if (priv_data->list) |
297 | 0 | { |
298 | | /* Check that the previous section has a footer */ |
299 | 0 | priv_entry = (busmaster_priv_t *)g_slist_last(priv_data->list)->data; |
300 | |
|
301 | 0 | if (priv_entry && priv_entry->file_end_offset == -1) |
302 | 0 | { |
303 | 0 | *err = WTAP_ERR_BAD_FILE; |
304 | 0 | *err_info = g_strdup("Footer is missing"); |
305 | 0 | return false; |
306 | 0 | } |
307 | 0 | } |
308 | | |
309 | | /* Start a new section */ |
310 | 0 | priv_entry = g_new(busmaster_priv_t, 1); |
311 | |
|
312 | 0 | priv_entry[0] = state.header; |
313 | 0 | priv_entry->file_start_offset = file_tell(wth->fh); |
314 | 0 | priv_entry->file_end_offset = -1; |
315 | |
|
316 | 0 | priv_data->list = g_slist_append(priv_data->list, priv_entry); |
317 | 0 | break; |
318 | 0 | case LOG_ENTRY_MSG: |
319 | 0 | is_msg = true; |
320 | 0 | priv_entry = busmaster_find_priv_entry(priv_data->list, *data_offset); |
321 | 0 | is_ok = busmaster_gen_packet(wth, rec, priv_entry, &state.msg, err, err_info); |
322 | 0 | break; |
323 | 0 | case LOG_ENTRY_EOF: |
324 | 0 | case LOG_ENTRY_ERROR: |
325 | 0 | case LOG_ENTRY_NONE: |
326 | 0 | default: |
327 | 0 | is_ok = false; |
328 | 0 | break; |
329 | 0 | } |
330 | 0 | } |
331 | | |
332 | 0 | ws_debug("%s: stopped at offset %" PRIi64 " with entry %d\n", |
333 | 0 | G_STRFUNC, file_tell(wth->fh), entry); |
334 | |
|
335 | 0 | return is_ok; |
336 | 0 | } |
337 | | |
338 | | static bool |
339 | | busmaster_seek_read(wtap *wth, int64_t seek_off, wtap_rec *rec, |
340 | | int *err, char **err_info) |
341 | 0 | { |
342 | 0 | busmaster_priv_t *priv_entry; |
343 | 0 | busmaster_state_t state = {0}; |
344 | 0 | log_entry_type_t entry; |
345 | 0 | busmaster_data_t* priv_data = (busmaster_data_t*)wtap_socketcan_get_private_data(wth); |
346 | |
|
347 | 0 | ws_debug("%s: offset = %" PRIi64 "\n", G_STRFUNC, seek_off); |
348 | |
|
349 | 0 | priv_entry = busmaster_find_priv_entry(priv_data->list, seek_off); |
350 | 0 | if (!priv_entry) |
351 | 0 | { |
352 | 0 | ws_debug("%s: analyzing output\n", G_STRFUNC); |
353 | 0 | *err = WTAP_ERR_BAD_FILE; |
354 | 0 | *err_info = g_strdup("Malformed header"); |
355 | 0 | return false; |
356 | 0 | } |
357 | | |
358 | 0 | if (file_seek(wth->random_fh, seek_off, SEEK_SET, err) == -1) |
359 | 0 | return false; |
360 | | |
361 | 0 | state.header = *priv_entry; |
362 | 0 | entry = busmaster_parse(wth->random_fh, &state, err, err_info); |
363 | |
|
364 | 0 | ws_debug("%s: analyzing output\n", G_STRFUNC); |
365 | |
|
366 | 0 | if (entry == LOG_ENTRY_ERROR || entry == LOG_ENTRY_NONE) |
367 | 0 | return false; |
368 | | |
369 | 0 | if (entry != LOG_ENTRY_MSG) |
370 | 0 | { |
371 | 0 | *err = WTAP_ERR_BAD_FILE; |
372 | 0 | *err_info = g_strdup("Failed to read a frame"); |
373 | 0 | return false; |
374 | 0 | } |
375 | | |
376 | 0 | return busmaster_gen_packet(wth, rec, priv_entry, &state.msg, err, err_info); |
377 | 0 | } |
378 | | |
379 | | static const struct supported_block_type busmaster_blocks_supported[] = { |
380 | | /* |
381 | | * We support packet blocks, with no comments or other options. |
382 | | */ |
383 | | { WTAP_BLOCK_PACKET, MULTIPLE_BLOCKS_SUPPORTED, NO_OPTIONS_SUPPORTED } |
384 | | }; |
385 | | |
386 | | static const struct file_type_subtype_info busmaster_info = { |
387 | | "BUSMASTER log file", "busmaster", "log", NULL, |
388 | | false, BLOCKS_SUPPORTED(busmaster_blocks_supported), |
389 | | NULL, NULL, NULL |
390 | | }; |
391 | | |
392 | | void register_busmaster(void) |
393 | 14 | { |
394 | 14 | busmaster_file_type_subtype = wtap_register_file_type_subtype(&busmaster_info); |
395 | 14 | } |
396 | | |
397 | | /* |
398 | | * Editor modelines - https://www.wireshark.org/tools/modelines.html |
399 | | * |
400 | | * Local variables: |
401 | | * c-basic-offset: 4 |
402 | | * tab-width: 8 |
403 | | * indent-tabs-mode: nil |
404 | | * End: |
405 | | * |
406 | | * vi: set shiftwidth=4 tabstop=8 expandtab: |
407 | | * :indentSize=4:tabSize=8:noTabs=true: |
408 | | */ |