Qt/ByteViewTab: document potential pitfall
[metze/wireshark/wip.git] / wiretap / camins.c
1 /* camins.c
2  *
3  * File format support for Rabbit Labs CAM Inspector files
4  * Copyright (c) 2013 by Martin Kaiser <martin@kaiser.cx>
5  *
6  * Wireshark - Network traffic analyzer
7  * By Gerald Combs <gerald@wireshark.org>
8  * Copyright 1998 Gerald Combs
9  *
10  * SPDX-License-Identifier: GPL-2.0-or-later
11  */
12
13
14 /* CAM Inspector is a commercial log tool for DVB-CI
15    it stores recorded packets between a CI module and a DVB receiver,
16    using a proprietary file format
17
18    a CAM Inspector file consists of 16bit blocks
19    the first byte contains payload data,
20    the second byte contains a "transaction type"
21
22    we currently support the following transaction types
23
24    0x20 == data transfer from CI module to host
25    0x22 == host reads the lower byte of the size register
26    0x23 == host reads the higher byte of the size register
27    0x2A == host writes the lower byte of the size register
28    0x2B == host writes the higher byte of the size register
29    0x28 == data transfer from host to CI module
30
31    using these transaction types, we can identify and assemble data transfers
32    from the host to the CAM and vice versa
33
34    a host->module data transfer will use the following transactions
35       one 0x2A and one 0x2B transaction to write the 16bit size
36       <size> 0x28 transactions to transfer one byte at a time
37    this will be assembled into one packet
38
39    the module->host transfer is similar
40
41    a CAM Inspector file uses a 44-bit time counter to keep track of the
42    time. the counter is in units of 1us. a timestamp block in the file
43    updates a part of the global time counter. a timestamp contains a 2-bit
44    relative position within the time counter and an 11-bit value for
45    this position.
46
47    error handling
48    when we run into an error while assembling a data transfer, the
49    primary goal is to recover so that we can handle the next transfer
50    correctly (all files I used for testing contained errors where
51    apparently the logging hardware missed some bytes)
52 */
53
54 #include "config.h"
55
56 #include <glib.h>
57 #include <string.h>
58 #include "wtap-int.h"
59 #include "file_wrappers.h"
60
61 #include "camins.h"
62
63
64 #define TRANS_CAM_HOST        0x20
65 #define TRANS_READ_SIZE_LOW   0x22
66 #define TRANS_READ_SIZE_HIGH  0x23
67 #define TRANS_HOST_CAM        0x28
68 #define TRANS_WRITE_SIZE_LOW  0x2A
69 #define TRANS_WRITE_SIZE_HIGH 0x2B
70
71 #define IS_TRANS_SIZE(x) \
72     ((x)==TRANS_WRITE_SIZE_LOW || (x)==TRANS_WRITE_SIZE_HIGH || \
73      (x)==TRANS_READ_SIZE_LOW || (x)==TRANS_READ_SIZE_HIGH)
74
75 /* a block contains a timestamp if the upper three bits are 0 */
76 #define IS_TIMESTAMP(x) (((x) & 0xE0) == 0x00)
77
78 /* a timestamp consists of a 2-bit position, followed by an 11-bit value. */
79 #define TS_VALUE_SHIFT  11
80 #define TS_POS_MASK     (0x3 << TS_VALUE_SHIFT)
81 #define TS_VALUE_MASK   G_GUINT64_CONSTANT((1 << TS_VALUE_SHIFT) - 1)
82
83 typedef enum {
84     SIZE_HAVE_NONE,
85     SIZE_HAVE_LOW,
86     SIZE_HAVE_HIGH,
87     SIZE_HAVE_ALL
88 } size_read_t;
89
90 #define RESET_STAT_VALS \
91 { \
92     *dat_trans_type = 0x00; \
93     *dat_len = 0x00; \
94     size_stat = SIZE_HAVE_NONE; \
95 }
96
97 #define SIZE_ADD_LOW \
98 { size_stat = (size_stat==SIZE_HAVE_HIGH ? SIZE_HAVE_ALL : SIZE_HAVE_LOW); }
99
100 #define SIZE_ADD_HIGH \
101 { size_stat = (size_stat==SIZE_HAVE_LOW ? SIZE_HAVE_ALL : SIZE_HAVE_HIGH); }
102
103 /* PCAP DVB-CI pseudo-header, see http://www.kaiser.cx/pcap-dvbci.html */
104 #define DVB_CI_PSEUDO_HDR_VER 0
105 #define DVB_CI_PSEUDO_HDR_LEN 4
106 #define DVB_CI_PSEUDO_HDR_CAM_TO_HOST 0xFF
107 #define DVB_CI_PSEUDO_HDR_HOST_TO_CAM 0xFE
108
109
110 /* Detect a camins file by looking at the blocks that access the 16bit
111    size register. The matching blocks to access the upper and lower 8bit
112    must be no further than 5 blocks apart.
113    A file may have errors that affect the size blocks. Therefore, we
114    read the entire file and require that we have much more valid pairs
115    than errors. */
116 static gboolean detect_camins_file(FILE_T fh)
117 {
118     int      err;
119     gchar   *err_info;
120     guint8   block[2];
121     guint8   search_block = 0;
122     guint8   gap_count = 0;
123     guint32  valid_pairs = 0, invalid_pairs = 0;
124
125     while (wtap_read_bytes(fh, block, sizeof(block), &err, &err_info)) {
126         if (err == WTAP_ERR_SHORT_READ)
127             break;
128
129        if (search_block != 0) {
130            /* We're searching for a matching block to complete the pair. */
131
132             if (block[1] == search_block) {
133                 /* We found it */
134                 valid_pairs++;
135                 search_block = 0;
136             }
137             else {
138                 /* We didn't find it. */
139                 gap_count++;
140                 if (gap_count > 5) {
141                     /* Give up the search, we have no pair. */
142                     invalid_pairs++;
143                     search_block = 0;
144                 }
145             }
146         }
147         else {
148             /* We're not searching for a matching block at the moment.
149                If we see a size read/write block of one type, the matching
150                block is the the other type and we can start searching. */
151
152             if (block[1] == TRANS_READ_SIZE_LOW) {
153                 search_block = TRANS_READ_SIZE_HIGH;
154                 gap_count = 0;
155             }
156             else if (block[1] == TRANS_READ_SIZE_HIGH) {
157                 search_block = TRANS_READ_SIZE_LOW;
158                 gap_count = 0;
159             }
160             else if (block[1] == TRANS_WRITE_SIZE_LOW) {
161                 search_block = TRANS_WRITE_SIZE_HIGH;
162                 gap_count = 0;
163             }
164             else if (block[1] == TRANS_WRITE_SIZE_HIGH) {
165                 search_block = TRANS_WRITE_SIZE_LOW;
166                 gap_count = 0;
167             }
168         }
169     }
170
171     /* For valid_pairs == invalid_pairs == 0, this isn't a camins file.
172        Don't change > into >= */
173     if (valid_pairs > 10 * invalid_pairs)
174         return TRUE;
175
176     return FALSE;
177 }
178
179
180 /* update the current time counter with infos from a timestamp block */
181 static void process_timestamp(guint16 timestamp, guint64 *time_us)
182 {
183     guint8 pos, shift;
184     guint64 val;
185
186     if (!time_us)
187         return;
188
189     val = timestamp & TS_VALUE_MASK;
190     pos = (timestamp & TS_POS_MASK) >> TS_VALUE_SHIFT;
191     shift = TS_VALUE_SHIFT * pos;
192
193     *time_us &= ~(TS_VALUE_MASK << shift);
194     *time_us |= (val << shift);
195 }
196
197
198 /* find the transaction type for the data bytes of the next packet
199    and the number of data bytes in that packet
200    the fd is moved such that it can be used in a subsequent call
201    to retrieve the data
202    if requested by the caller, we increment the time counter as we
203    walk through the file */
204 static gboolean
205 find_next_pkt_info(FILE_T fh,
206         guint8 *dat_trans_type, /* transaction type used for the data bytes */
207         guint16 *dat_len,       /* the number of data bytes in the packet */
208         guint64 *time_us,
209         int *err, gchar **err_info)
210 {
211     guint8       block[2];
212     size_read_t  size_stat;
213
214     if (!dat_trans_type || !dat_len)
215         return FALSE;
216
217     RESET_STAT_VALS;
218
219     do {
220         if (!wtap_read_bytes_or_eof(fh, block, sizeof(block), err, err_info)) {
221             RESET_STAT_VALS;
222             return FALSE;
223         }
224
225         /* our strategy is to continue reading until we have a high and a
226            low size byte for the same direction, duplicates or spurious data
227            bytes are ignored */
228
229         switch (block[1]) {
230             case TRANS_READ_SIZE_LOW:
231                 if (*dat_trans_type != TRANS_CAM_HOST)
232                     RESET_STAT_VALS;
233                 *dat_trans_type = TRANS_CAM_HOST;
234                 *dat_len |= block[0];
235                 SIZE_ADD_LOW;
236                 break;
237             case TRANS_READ_SIZE_HIGH:
238                 if (*dat_trans_type != TRANS_CAM_HOST)
239                     RESET_STAT_VALS;
240                 *dat_trans_type = TRANS_CAM_HOST;
241                 *dat_len |= (block[0] << 8);
242                 SIZE_ADD_HIGH;
243                 break;
244             case TRANS_WRITE_SIZE_LOW:
245                 if (*dat_trans_type != TRANS_HOST_CAM)
246                     RESET_STAT_VALS;
247                 *dat_trans_type = TRANS_HOST_CAM;
248                 *dat_len |= block[0];
249                 SIZE_ADD_LOW;
250                 break;
251             case TRANS_WRITE_SIZE_HIGH:
252                 if (*dat_trans_type != TRANS_HOST_CAM)
253                     RESET_STAT_VALS;
254                 *dat_trans_type = TRANS_HOST_CAM;
255                 *dat_len |= (block[0] << 8);
256                 SIZE_ADD_HIGH;
257                 break;
258             default:
259                 if (IS_TIMESTAMP(block[1]))
260                     process_timestamp(pletoh16(block), time_us);
261                 break;
262         }
263     } while (size_stat != SIZE_HAVE_ALL);
264
265     return TRUE;
266 }
267
268
269 /* buffer allocated by the caller, must be long enough to hold
270    dat_len bytes, ... */
271 static gint
272 read_packet_data(FILE_T fh, guint8 dat_trans_type, guint8 *buf, guint16 dat_len,
273                  guint64 *time_us, int *err, gchar **err_info)
274 {
275     guint8  *p;
276     guint8   block[2];
277     guint16  bytes_count = 0;
278
279     if (!buf)
280         return -1;
281
282     /* we're not checking for end-of-file here, we read as many bytes as
283        we can get (up to dat_len) and return those
284        end-of-file will be detected when we search for the next packet */
285
286     p = buf;
287     while (bytes_count < dat_len) {
288         if (!wtap_read_bytes_or_eof(fh, block, sizeof(block), err, err_info))
289             break;
290
291         if (block[1] == dat_trans_type) {
292             *p++ = block[0];
293             bytes_count++;
294         }
295         else if (IS_TIMESTAMP(block[1])) {
296                 process_timestamp(pletoh16(block), time_us);
297         }
298         else if (IS_TRANS_SIZE(block[1])) {
299             /* go back before the size transaction block
300                the next packet should be able to pick up this block */
301             if (-1 == file_seek(fh, -(gint64)sizeof(block), SEEK_CUR, err))
302                 return -1;
303             break;
304         }
305     }
306
307     return bytes_count;
308 }
309
310
311 /* create a DVB-CI pseudo header
312    return its length or -1 for error */
313 static gint
314 create_pseudo_hdr(guint8 *buf, guint8 dat_trans_type, guint16 dat_len)
315 {
316     if (!buf)
317         return -1;
318
319     buf[0] = DVB_CI_PSEUDO_HDR_VER;
320
321     if (dat_trans_type==TRANS_CAM_HOST)
322         buf[1] = DVB_CI_PSEUDO_HDR_CAM_TO_HOST;
323     else if (dat_trans_type==TRANS_HOST_CAM)
324         buf[1] = DVB_CI_PSEUDO_HDR_HOST_TO_CAM;
325     else
326         return -1;
327
328     buf[2] = (dat_len>>8) & 0xFF;
329     buf[3] = dat_len & 0xFF;
330
331     return DVB_CI_PSEUDO_HDR_LEN;
332 }
333
334
335 static gboolean
336 camins_read_packet(FILE_T fh, wtap_rec *rec, Buffer *buf,
337     guint64 *time_us, int *err, gchar **err_info)
338 {
339     guint8      dat_trans_type;
340     guint16     dat_len;
341     guint8     *p;
342     gint        offset, bytes_read;
343
344     if (!find_next_pkt_info(
345                 fh, &dat_trans_type, &dat_len, time_us, err, err_info))
346         return FALSE;
347     /*
348      * The maximum value of length is 65535, which, even after
349      * DVB_CI_PSEUDO_HDR_LEN is added to it, is less than
350      * WTAP_MAX_PACKET_SIZE_STANDARD will ever be, so we don't need to check
351      * it.
352      */
353
354     ws_buffer_assure_space(buf, DVB_CI_PSEUDO_HDR_LEN+dat_len);
355     p = ws_buffer_start_ptr(buf);
356     /* NULL check for p is done in create_pseudo_hdr() */
357     offset = create_pseudo_hdr(p, dat_trans_type, dat_len);
358     if (offset<0) {
359         /* shouldn't happen, all invalid packets must be detected by
360            find_next_pkt_info() */
361         *err = WTAP_ERR_INTERNAL;
362         return FALSE;
363     }
364
365     bytes_read = read_packet_data(fh, dat_trans_type,
366             &p[offset], dat_len, time_us, err, err_info);
367     /* 0<=bytes_read<=dat_len is very likely a corrupted packet
368        we let the dissector handle this */
369     if (bytes_read < 0)
370         return FALSE;
371     offset += bytes_read;
372
373     rec->rec_type = REC_TYPE_PACKET;
374     rec->rec_header.packet_header.pkt_encap = WTAP_ENCAP_DVBCI;
375     if (time_us) {
376         rec->presence_flags = WTAP_HAS_TS;
377         rec->ts.secs = (time_t)(*time_us / (1000 * 1000));
378         rec->ts.nsecs = (int)(*time_us % (1000 *1000) * 1000);
379     }
380     rec->rec_header.packet_header.caplen = offset;
381     rec->rec_header.packet_header.len = offset;
382
383     return TRUE;
384 }
385
386
387 static gboolean
388 camins_read(wtap *wth, int *err, gchar **err_info, gint64 *data_offset)
389 {
390     *data_offset = file_tell(wth->fh);
391
392     return camins_read_packet(wth->fh, &wth->rec, wth->rec_data,
393         (guint64 *)(wth->priv), err, err_info);
394 }
395
396
397 static gboolean
398 camins_seek_read(wtap *wth, gint64 seek_off, wtap_rec *rec, Buffer *buf,
399                  int *err, gchar **err_info)
400 {
401     if (-1 == file_seek(wth->random_fh, seek_off, SEEK_SET, err))
402         return FALSE;
403
404     return camins_read_packet(wth->random_fh, rec, buf, NULL, err, err_info);
405 }
406
407
408 wtap_open_return_val camins_open(wtap *wth, int *err, gchar **err_info _U_)
409 {
410     if (!detect_camins_file(wth->fh))
411         return WTAP_OPEN_NOT_MINE;   /* no CAM Inspector file */
412
413     /* rewind the fh so we re-read from the beginning */
414     if (-1 == file_seek(wth->fh, 0, SEEK_SET, err))
415         return WTAP_OPEN_ERROR;
416
417    wth->file_encap = WTAP_ENCAP_DVBCI;
418    wth->snapshot_length = 0;
419    wth->file_tsprec = WTAP_TSPREC_USEC;
420
421    /* wth->priv stores a pointer to the global time counter. we update
422       it as we go through the file sequentially. */
423    wth->priv = g_malloc0(sizeof(guint64));
424
425    wth->subtype_read = camins_read;
426    wth->subtype_seek_read = camins_seek_read;
427    wth->file_type_subtype = WTAP_FILE_TYPE_SUBTYPE_CAMINS;
428
429    *err = 0;
430    return WTAP_OPEN_MINE;
431 }
432
433
434 /*
435  * Editor modelines  -  http://www.wireshark.org/tools/modelines.html
436  *
437  * Local variables:
438  * c-basic-offset: 4
439  * tab-width: 8
440  * indent-tabs-mode: nil
441  * End:
442  *
443  * vi: set shiftwidth=4 tabstop=8 expandtab:
444  * :indentSize=4:tabSize=8:noTabs=true:
445  */