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 https://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 /* Maximum number of bytes to read before making a heuristic decision
110  * of whether this is our file type or not. Arbitrary. */
111 #define CAMINS_BYTES_TO_CHECK 0x3FFFFFFFU
112 
113 static int camins_file_type_subtype = -1;
114 
115 void register_camins(void);
116 
117 /* Detect a camins file by looking at the blocks that access the 16bit
118    size register. The matching blocks to access the upper and lower 8bit
119    must be no further than 5 blocks apart.
120    A file may have errors that affect the size blocks. Therefore, we
121    read CAMINS_BYTES_TO_CHECK bytes and require that we have many more
122    valid pairs than errors. */
123 static wtap_open_return_val detect_camins_file(FILE_T fh)
124 {
125     int      err;
126     gchar   *err_info;
127     guint8   block[2];
128     guint8   search_block = 0;
129     guint8   gap_count = 0;
130     guint32  valid_pairs = 0, invalid_pairs = 0;
131     guint64  read_bytes = 0;
132 
133     while (wtap_read_bytes(fh, block, sizeof(block), &err, &err_info)) {
134        if (search_block != 0) {
135            /* We're searching for a matching block to complete the pair. */
136 
137             if (block[1] == search_block) {
138                 /* We found it */
139                 valid_pairs++;
140                 search_block = 0;
141             }
142             else {
143                 /* We didn't find it. */
144                 gap_count++;
145                 if (gap_count > 5) {
146                     /* Give up the search, we have no pair. */
147                     invalid_pairs++;
148                     search_block = 0;
149                 }
150             }
151         }
152         else {
153             /* We're not searching for a matching block at the moment.
154                If we see a size read/write block of one type, the matching
155                block is the the other type and we can start searching. */
156 
157             if (block[1] == TRANS_READ_SIZE_LOW) {
158                 search_block = TRANS_READ_SIZE_HIGH;
159                 gap_count = 0;
160             }
161             else if (block[1] == TRANS_READ_SIZE_HIGH) {
162                 search_block = TRANS_READ_SIZE_LOW;
163                 gap_count = 0;
164             }
165             else if (block[1] == TRANS_WRITE_SIZE_LOW) {
166                 search_block = TRANS_WRITE_SIZE_HIGH;
167                 gap_count = 0;
168             }
169             else if (block[1] == TRANS_WRITE_SIZE_HIGH) {
170                 search_block = TRANS_WRITE_SIZE_LOW;
171                 gap_count = 0;
172             }
173         }
174         read_bytes += sizeof(block);
175         if (read_bytes > CAMINS_BYTES_TO_CHECK) {
176             err = 0;
177             break;
178         }
179     }
180 
181     if ((err != 0) && (err != WTAP_ERR_SHORT_READ)) {
182         /* A real read error. */
183         return WTAP_OPEN_ERROR;
184     }
185 
186     /* For valid_pairs == invalid_pairs == 0, this isn't a camins file.
187        Don't change > into >= */
188     if (valid_pairs > 10 * invalid_pairs)
189         return WTAP_OPEN_MINE;
190 
191     return WTAP_OPEN_NOT_MINE;
192 }
193 
194 
195 /* update the current time counter with infos from a timestamp block */
196 static void process_timestamp(guint16 timestamp, guint64 *time_us)
197 {
198     guint8 pos, shift;
199     guint64 val;
200 
201     if (!time_us)
202         return;
203 
204     val = timestamp & TS_VALUE_MASK;
205     pos = (timestamp & TS_POS_MASK) >> TS_VALUE_SHIFT;
206     shift = TS_VALUE_SHIFT * pos;
207 
208     *time_us &= ~(TS_VALUE_MASK << shift);
209     *time_us |= (val << shift);
210 }
211 
212 
213 /* find the transaction type for the data bytes of the next packet
214    and the number of data bytes in that packet
215    the fd is moved such that it can be used in a subsequent call
216    to retrieve the data
217    if requested by the caller, we increment the time counter as we
218    walk through the file */
219 static gboolean
220 find_next_pkt_info(FILE_T fh,
221         guint8 *dat_trans_type, /* transaction type used for the data bytes */
222         guint16 *dat_len,       /* the number of data bytes in the packet */
223         guint64 *time_us,
224         int *err, gchar **err_info)
225 {
226     guint8       block[2];
227     size_read_t  size_stat;
228 
229     if (!dat_trans_type || !dat_len)
230         return FALSE;
231 
232     RESET_STAT_VALS;
233 
234     do {
235         if (!wtap_read_bytes_or_eof(fh, block, sizeof(block), err, err_info)) {
236             RESET_STAT_VALS;
237             return FALSE;
238         }
239 
240         /* our strategy is to continue reading until we have a high and a
241            low size byte for the same direction, duplicates or spurious data
242            bytes are ignored */
243 
244         switch (block[1]) {
245             case TRANS_READ_SIZE_LOW:
246                 if (*dat_trans_type != TRANS_CAM_HOST)
247                     RESET_STAT_VALS;
248                 *dat_trans_type = TRANS_CAM_HOST;
249                 *dat_len |= block[0];
250                 SIZE_ADD_LOW;
251                 break;
252             case TRANS_READ_SIZE_HIGH:
253                 if (*dat_trans_type != TRANS_CAM_HOST)
254                     RESET_STAT_VALS;
255                 *dat_trans_type = TRANS_CAM_HOST;
256                 *dat_len |= (block[0] << 8);
257                 SIZE_ADD_HIGH;
258                 break;
259             case TRANS_WRITE_SIZE_LOW:
260                 if (*dat_trans_type != TRANS_HOST_CAM)
261                     RESET_STAT_VALS;
262                 *dat_trans_type = TRANS_HOST_CAM;
263                 *dat_len |= block[0];
264                 SIZE_ADD_LOW;
265                 break;
266             case TRANS_WRITE_SIZE_HIGH:
267                 if (*dat_trans_type != TRANS_HOST_CAM)
268                     RESET_STAT_VALS;
269                 *dat_trans_type = TRANS_HOST_CAM;
270                 *dat_len |= (block[0] << 8);
271                 SIZE_ADD_HIGH;
272                 break;
273             default:
274                 if (IS_TIMESTAMP(block[1]))
275                     process_timestamp(pletoh16(block), time_us);
276                 break;
277         }
278     } while (size_stat != SIZE_HAVE_ALL);
279 
280     return TRUE;
281 }
282 
283 
284 /* buffer allocated by the caller, must be long enough to hold
285    dat_len bytes, ... */
286 static gint
287 read_packet_data(FILE_T fh, guint8 dat_trans_type, guint8 *buf, guint16 dat_len,
288                  guint64 *time_us, int *err, gchar **err_info)
289 {
290     guint8  *p;
291     guint8   block[2];
292     guint16  bytes_count = 0;
293 
294     if (!buf)
295         return -1;
296 
297     /* we're not checking for end-of-file here, we read as many bytes as
298        we can get (up to dat_len) and return those
299        end-of-file will be detected when we search for the next packet */
300 
301     p = buf;
302     while (bytes_count < dat_len) {
303         if (!wtap_read_bytes_or_eof(fh, block, sizeof(block), err, err_info))
304             break;
305 
306         if (block[1] == dat_trans_type) {
307             *p++ = block[0];
308             bytes_count++;
309         }
310         else if (IS_TIMESTAMP(block[1])) {
311                 process_timestamp(pletoh16(block), time_us);
312         }
313         else if (IS_TRANS_SIZE(block[1])) {
314             /* go back before the size transaction block
315                the next packet should be able to pick up this block */
316             if (-1 == file_seek(fh, -(gint64)sizeof(block), SEEK_CUR, err))
317                 return -1;
318             break;
319         }
320     }
321 
322     return bytes_count;
323 }
324 
325 
326 /* create a DVB-CI pseudo header
327    return its length or -1 for error */
328 static gint
329 create_pseudo_hdr(guint8 *buf, guint8 dat_trans_type, guint16 dat_len,
330     gchar **err_info)
331 {
332     buf[0] = DVB_CI_PSEUDO_HDR_VER;
333 
334     if (dat_trans_type==TRANS_CAM_HOST)
335         buf[1] = DVB_CI_PSEUDO_HDR_CAM_TO_HOST;
336     else if (dat_trans_type==TRANS_HOST_CAM)
337         buf[1] = DVB_CI_PSEUDO_HDR_HOST_TO_CAM;
338     else {
339         *err_info = g_strdup_printf("camins: invalid dat_trans_type %u", dat_trans_type);
340         return -1;
341     }
342 
343     buf[2] = (dat_len>>8) & 0xFF;
344     buf[3] = dat_len & 0xFF;
345 
346     return DVB_CI_PSEUDO_HDR_LEN;
347 }
348 
349 
350 static gboolean
351 camins_read_packet(FILE_T fh, wtap_rec *rec, Buffer *buf,
352     guint64 *time_us, int *err, gchar **err_info)
353 {
354     guint8      dat_trans_type;
355     guint16     dat_len;
356     guint8     *p;
357     gint        offset, bytes_read;
358 
359     if (!find_next_pkt_info(
360                 fh, &dat_trans_type, &dat_len, time_us, err, err_info))
361         return FALSE;
362     /*
363      * The maximum value of length is 65535, which, even after
364      * DVB_CI_PSEUDO_HDR_LEN is added to it, is less than
365      * WTAP_MAX_PACKET_SIZE_STANDARD will ever be, so we don't need to check
366      * it.
367      */
368 
369     ws_buffer_assure_space(buf, DVB_CI_PSEUDO_HDR_LEN+dat_len);
370     p = ws_buffer_start_ptr(buf);
371     offset = create_pseudo_hdr(p, dat_trans_type, dat_len, err_info);
372     if (offset<0) {
373         /* shouldn't happen, all invalid packets must be detected by
374            find_next_pkt_info() */
375         *err = WTAP_ERR_INTERNAL;
376         /* create_pseudo_hdr() set err_info appropriately */
377         return FALSE;
378     }
379 
380     bytes_read = read_packet_data(fh, dat_trans_type,
381             &p[offset], dat_len, time_us, err, err_info);
382     /* 0<=bytes_read<=dat_len is very likely a corrupted packet
383        we let the dissector handle this */
384     if (bytes_read < 0)
385         return FALSE;
386     offset += bytes_read;
387 
388     rec->rec_type = REC_TYPE_PACKET;
389     rec->block = wtap_block_create(WTAP_BLOCK_PACKET);
390     rec->presence_flags = 0; /* we may or may not have a time stamp */
391     rec->rec_header.packet_header.pkt_encap = WTAP_ENCAP_DVBCI;
392     if (time_us) {
393         rec->presence_flags = WTAP_HAS_TS;
394         rec->ts.secs = (time_t)(*time_us / (1000 * 1000));
395         rec->ts.nsecs = (int)(*time_us % (1000 *1000) * 1000);
396     }
397     rec->rec_header.packet_header.caplen = offset;
398     rec->rec_header.packet_header.len = offset;
399 
400     return TRUE;
401 }
402 
403 
404 static gboolean
405 camins_read(wtap *wth, wtap_rec *rec, Buffer *buf, int *err,
406     gchar **err_info, gint64 *data_offset)
407 {
408     *data_offset = file_tell(wth->fh);
409 
410     return camins_read_packet(wth->fh, rec, buf, (guint64 *)(wth->priv),
411                               err, err_info);
412 }
413 
414 
415 static gboolean
416 camins_seek_read(wtap *wth, gint64 seek_off, wtap_rec *rec, Buffer *buf,
417                  int *err, gchar **err_info)
418 {
419     if (-1 == file_seek(wth->random_fh, seek_off, SEEK_SET, err))
420         return FALSE;
421 
422     return camins_read_packet(wth->random_fh, rec, buf, NULL, err, err_info);
423 }
424 
425 
426 wtap_open_return_val camins_open(wtap *wth, int *err, gchar **err_info _U_)
427 {
428     wtap_open_return_val status;
429 
430     status = detect_camins_file(wth->fh);
431     if (status != WTAP_OPEN_MINE) {
432         /* A read error or a failed heuristic. */
433         return status;
434     }
435 
436     /* rewind the fh so we re-read from the beginning */
437     if (-1 == file_seek(wth->fh, 0, SEEK_SET, err))
438         return WTAP_OPEN_ERROR;
439 
440    wth->file_encap = WTAP_ENCAP_DVBCI;
441    wth->snapshot_length = 0;
442    wth->file_tsprec = WTAP_TSPREC_USEC;
443 
444    /* wth->priv stores a pointer to the global time counter. we update
445       it as we go through the file sequentially. */
446    wth->priv = g_new0(guint64, 1);
447 
448    wth->subtype_read = camins_read;
449    wth->subtype_seek_read = camins_seek_read;
450    wth->file_type_subtype = camins_file_type_subtype;
451 
452    *err = 0;
453 
454    /*
455     * Add an IDB; we don't know how many interfaces were
456     * involved, so we just say one interface, about which
457     * we only know the link-layer type, snapshot length,
458     * and time stamp resolution.
459     */
460    wtap_add_generated_idb(wth);
461 
462    return WTAP_OPEN_MINE;
463 }
464 
465 static const struct supported_block_type camins_blocks_supported[] = {
466    /*
467     * We support packet blocks, with no comments or other options.
468     */
469    { WTAP_BLOCK_PACKET, MULTIPLE_BLOCKS_SUPPORTED, NO_OPTIONS_SUPPORTED }
470 };
471 
472 static const struct file_type_subtype_info camins_info = {
473    "CAM Inspector file", "camins", "camins", NULL,
474    FALSE, BLOCKS_SUPPORTED(camins_blocks_supported),
475    NULL, NULL, NULL
476 };
477 
478 void register_camins(void)
479 {
480    camins_file_type_subtype = wtap_register_file_type_subtype(&camins_info);
481 
482    /*
483     * Register name for backwards compatibility with the
484     * wtap_filetypes table in Lua.
485     */
486    wtap_register_backwards_compatibility_lua_name("CAMINS",
487                                                   camins_file_type_subtype);
488 }
489 
490 /*
491  * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
492  *
493  * Local variables:
494  * c-basic-offset: 4
495  * tab-width: 8
496  * indent-tabs-mode: nil
497  * End:
498  *
499  * vi: set shiftwidth=4 tabstop=8 expandtab:
500  * :indentSize=4:tabSize=8:noTabs=true:
501  */
502