Coverage Report

Created: 2025-08-04 07:15

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