From e4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 10 Apr 2024 22:34:10 +0200 Subject: Adding upstream version 4.2.2. Signed-off-by: Daniel Baumann --- wiretap/camins.c | 501 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 501 insertions(+) create mode 100644 wiretap/camins.c (limited to 'wiretap/camins.c') diff --git a/wiretap/camins.c b/wiretap/camins.c new file mode 100644 index 00000000..27d26111 --- /dev/null +++ b/wiretap/camins.c @@ -0,0 +1,501 @@ +/* camins.c + * + * File format support for Rabbit Labs CAM Inspector files + * Copyright (c) 2013 by Martin Kaiser + * + * Wireshark - Network traffic analyzer + * By Gerald Combs + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + + +/* CAM Inspector is a commercial log tool for DVB-CI + it stores recorded packets between a CI module and a DVB receiver, + using a proprietary file format + + a CAM Inspector file consists of 16bit blocks + the first byte contains payload data, + the second byte contains a "transaction type" + + we currently support the following transaction types + + 0x20 == data transfer from CI module to host + 0x22 == host reads the lower byte of the size register + 0x23 == host reads the higher byte of the size register + 0x2A == host writes the lower byte of the size register + 0x2B == host writes the higher byte of the size register + 0x28 == data transfer from host to CI module + + using these transaction types, we can identify and assemble data transfers + from the host to the CAM and vice versa + + a host->module data transfer will use the following transactions + one 0x2A and one 0x2B transaction to write the 16bit size + 0x28 transactions to transfer one byte at a time + this will be assembled into one packet + + the module->host transfer is similar + + a CAM Inspector file uses a 44-bit time counter to keep track of the + time. the counter is in units of 1us. a timestamp block in the file + updates a part of the global time counter. a timestamp contains a 2-bit + relative position within the time counter and an 11-bit value for + this position. + + error handling + when we run into an error while assembling a data transfer, the + primary goal is to recover so that we can handle the next transfer + correctly (all files I used for testing contained errors where + apparently the logging hardware missed some bytes) +*/ + +#include "config.h" + +#include +#include +#include "wtap-int.h" +#include "file_wrappers.h" + +#include "camins.h" + + +#define TRANS_CAM_HOST 0x20 +#define TRANS_READ_SIZE_LOW 0x22 +#define TRANS_READ_SIZE_HIGH 0x23 +#define TRANS_HOST_CAM 0x28 +#define TRANS_WRITE_SIZE_LOW 0x2A +#define TRANS_WRITE_SIZE_HIGH 0x2B + +#define IS_TRANS_SIZE(x) \ + ((x)==TRANS_WRITE_SIZE_LOW || (x)==TRANS_WRITE_SIZE_HIGH || \ + (x)==TRANS_READ_SIZE_LOW || (x)==TRANS_READ_SIZE_HIGH) + +/* a block contains a timestamp if the upper three bits are 0 */ +#define IS_TIMESTAMP(x) (((x) & 0xE0) == 0x00) + +/* a timestamp consists of a 2-bit position, followed by an 11-bit value. */ +#define TS_VALUE_SHIFT 11 +#define TS_POS_MASK (0x3 << TS_VALUE_SHIFT) +#define TS_VALUE_MASK G_GUINT64_CONSTANT((1 << TS_VALUE_SHIFT) - 1) + +typedef enum { + SIZE_HAVE_NONE, + SIZE_HAVE_LOW, + SIZE_HAVE_HIGH, + SIZE_HAVE_ALL +} size_read_t; + +#define RESET_STAT_VALS \ +{ \ + *dat_trans_type = 0x00; \ + *dat_len = 0x00; \ + size_stat = SIZE_HAVE_NONE; \ +} + +#define SIZE_ADD_LOW \ +{ size_stat = (size_stat==SIZE_HAVE_HIGH ? SIZE_HAVE_ALL : SIZE_HAVE_LOW); } + +#define SIZE_ADD_HIGH \ +{ size_stat = (size_stat==SIZE_HAVE_LOW ? SIZE_HAVE_ALL : SIZE_HAVE_HIGH); } + +/* PCAP DVB-CI pseudo-header, see https://www.kaiser.cx/pcap-dvbci.html */ +#define DVB_CI_PSEUDO_HDR_VER 0 +#define DVB_CI_PSEUDO_HDR_LEN 4 +#define DVB_CI_PSEUDO_HDR_CAM_TO_HOST 0xFF +#define DVB_CI_PSEUDO_HDR_HOST_TO_CAM 0xFE + +/* Maximum number of bytes to read before making a heuristic decision + * of whether this is our file type or not. Arbitrary. */ +#define CAMINS_BYTES_TO_CHECK 0x3FFFFFFFU + +static int camins_file_type_subtype = -1; + +void register_camins(void); + +/* Detect a camins file by looking at the blocks that access the 16bit + size register. The matching blocks to access the upper and lower 8bit + must be no further than 5 blocks apart. + A file may have errors that affect the size blocks. Therefore, we + read CAMINS_BYTES_TO_CHECK bytes and require that we have many more + valid pairs than errors. */ +static wtap_open_return_val detect_camins_file(FILE_T fh) +{ + int err; + gchar *err_info; + guint8 block[2]; + guint8 search_block = 0; + guint8 gap_count = 0; + guint32 valid_pairs = 0, invalid_pairs = 0; + guint64 read_bytes = 0; + + while (wtap_read_bytes(fh, block, sizeof(block), &err, &err_info)) { + if (search_block != 0) { + /* We're searching for a matching block to complete the pair. */ + + if (block[1] == search_block) { + /* We found it */ + valid_pairs++; + search_block = 0; + } + else { + /* We didn't find it. */ + gap_count++; + if (gap_count > 5) { + /* Give up the search, we have no pair. */ + invalid_pairs++; + search_block = 0; + } + } + } + else { + /* We're not searching for a matching block at the moment. + If we see a size read/write block of one type, the matching + block is the other type and we can start searching. */ + + if (block[1] == TRANS_READ_SIZE_LOW) { + search_block = TRANS_READ_SIZE_HIGH; + gap_count = 0; + } + else if (block[1] == TRANS_READ_SIZE_HIGH) { + search_block = TRANS_READ_SIZE_LOW; + gap_count = 0; + } + else if (block[1] == TRANS_WRITE_SIZE_LOW) { + search_block = TRANS_WRITE_SIZE_HIGH; + gap_count = 0; + } + else if (block[1] == TRANS_WRITE_SIZE_HIGH) { + search_block = TRANS_WRITE_SIZE_LOW; + gap_count = 0; + } + } + read_bytes += sizeof(block); + if (read_bytes > CAMINS_BYTES_TO_CHECK) { + err = 0; + break; + } + } + + if ((err != 0) && (err != WTAP_ERR_SHORT_READ)) { + /* A real read error. */ + return WTAP_OPEN_ERROR; + } + + /* For valid_pairs == invalid_pairs == 0, this isn't a camins file. + Don't change > into >= */ + if (valid_pairs > 10 * invalid_pairs) + return WTAP_OPEN_MINE; + + return WTAP_OPEN_NOT_MINE; +} + + +/* update the current time counter with infos from a timestamp block */ +static void process_timestamp(guint16 timestamp, guint64 *time_us) +{ + guint8 pos, shift; + guint64 val; + + if (!time_us) + return; + + val = timestamp & TS_VALUE_MASK; + pos = (timestamp & TS_POS_MASK) >> TS_VALUE_SHIFT; + shift = TS_VALUE_SHIFT * pos; + + *time_us &= ~(TS_VALUE_MASK << shift); + *time_us |= (val << shift); +} + + +/* find the transaction type for the data bytes of the next packet + and the number of data bytes in that packet + the fd is moved such that it can be used in a subsequent call + to retrieve the data + if requested by the caller, we increment the time counter as we + walk through the file */ +static gboolean +find_next_pkt_info(FILE_T fh, + guint8 *dat_trans_type, /* transaction type used for the data bytes */ + guint16 *dat_len, /* the number of data bytes in the packet */ + guint64 *time_us, + int *err, gchar **err_info) +{ + guint8 block[2]; + size_read_t size_stat; + + if (!dat_trans_type || !dat_len) + return FALSE; + + RESET_STAT_VALS; + + do { + if (!wtap_read_bytes_or_eof(fh, block, sizeof(block), err, err_info)) { + RESET_STAT_VALS; + return FALSE; + } + + /* our strategy is to continue reading until we have a high and a + low size byte for the same direction, duplicates or spurious data + bytes are ignored */ + + switch (block[1]) { + case TRANS_READ_SIZE_LOW: + if (*dat_trans_type != TRANS_CAM_HOST) + RESET_STAT_VALS; + *dat_trans_type = TRANS_CAM_HOST; + *dat_len |= block[0]; + SIZE_ADD_LOW; + break; + case TRANS_READ_SIZE_HIGH: + if (*dat_trans_type != TRANS_CAM_HOST) + RESET_STAT_VALS; + *dat_trans_type = TRANS_CAM_HOST; + *dat_len |= (block[0] << 8); + SIZE_ADD_HIGH; + break; + case TRANS_WRITE_SIZE_LOW: + if (*dat_trans_type != TRANS_HOST_CAM) + RESET_STAT_VALS; + *dat_trans_type = TRANS_HOST_CAM; + *dat_len |= block[0]; + SIZE_ADD_LOW; + break; + case TRANS_WRITE_SIZE_HIGH: + if (*dat_trans_type != TRANS_HOST_CAM) + RESET_STAT_VALS; + *dat_trans_type = TRANS_HOST_CAM; + *dat_len |= (block[0] << 8); + SIZE_ADD_HIGH; + break; + default: + if (IS_TIMESTAMP(block[1])) + process_timestamp(pletoh16(block), time_us); + break; + } + } while (size_stat != SIZE_HAVE_ALL); + + return TRUE; +} + + +/* buffer allocated by the caller, must be long enough to hold + dat_len bytes, ... */ +static gint +read_packet_data(FILE_T fh, guint8 dat_trans_type, guint8 *buf, guint16 dat_len, + guint64 *time_us, int *err, gchar **err_info) +{ + guint8 *p; + guint8 block[2]; + guint16 bytes_count = 0; + + if (!buf) + return -1; + + /* we're not checking for end-of-file here, we read as many bytes as + we can get (up to dat_len) and return those + end-of-file will be detected when we search for the next packet */ + + p = buf; + while (bytes_count < dat_len) { + if (!wtap_read_bytes_or_eof(fh, block, sizeof(block), err, err_info)) + break; + + if (block[1] == dat_trans_type) { + *p++ = block[0]; + bytes_count++; + } + else if (IS_TIMESTAMP(block[1])) { + process_timestamp(pletoh16(block), time_us); + } + else if (IS_TRANS_SIZE(block[1])) { + /* go back before the size transaction block + the next packet should be able to pick up this block */ + if (-1 == file_seek(fh, -(gint64)sizeof(block), SEEK_CUR, err)) + return -1; + break; + } + } + + return bytes_count; +} + + +/* create a DVB-CI pseudo header + return its length or -1 for error */ +static gint +create_pseudo_hdr(guint8 *buf, guint8 dat_trans_type, guint16 dat_len, + gchar **err_info) +{ + buf[0] = DVB_CI_PSEUDO_HDR_VER; + + if (dat_trans_type==TRANS_CAM_HOST) + buf[1] = DVB_CI_PSEUDO_HDR_CAM_TO_HOST; + else if (dat_trans_type==TRANS_HOST_CAM) + buf[1] = DVB_CI_PSEUDO_HDR_HOST_TO_CAM; + else { + *err_info = ws_strdup_printf("camins: invalid dat_trans_type %u", dat_trans_type); + return -1; + } + + buf[2] = (dat_len>>8) & 0xFF; + buf[3] = dat_len & 0xFF; + + return DVB_CI_PSEUDO_HDR_LEN; +} + + +static gboolean +camins_read_packet(FILE_T fh, wtap_rec *rec, Buffer *buf, + guint64 *time_us, int *err, gchar **err_info) +{ + guint8 dat_trans_type; + guint16 dat_len; + guint8 *p; + gint offset, bytes_read; + + if (!find_next_pkt_info( + fh, &dat_trans_type, &dat_len, time_us, err, err_info)) + return FALSE; + /* + * The maximum value of length is 65535, which, even after + * DVB_CI_PSEUDO_HDR_LEN is added to it, is less than + * WTAP_MAX_PACKET_SIZE_STANDARD will ever be, so we don't need to check + * it. + */ + + ws_buffer_assure_space(buf, DVB_CI_PSEUDO_HDR_LEN+dat_len); + p = ws_buffer_start_ptr(buf); + offset = create_pseudo_hdr(p, dat_trans_type, dat_len, err_info); + if (offset<0) { + /* shouldn't happen, all invalid packets must be detected by + find_next_pkt_info() */ + *err = WTAP_ERR_INTERNAL; + /* create_pseudo_hdr() set err_info appropriately */ + return FALSE; + } + + bytes_read = read_packet_data(fh, dat_trans_type, + &p[offset], dat_len, time_us, err, err_info); + /* 0<=bytes_read<=dat_len is very likely a corrupted packet + we let the dissector handle this */ + if (bytes_read < 0) + return FALSE; + offset += bytes_read; + + rec->rec_type = REC_TYPE_PACKET; + rec->block = wtap_block_create(WTAP_BLOCK_PACKET); + rec->presence_flags = 0; /* we may or may not have a time stamp */ + rec->rec_header.packet_header.pkt_encap = WTAP_ENCAP_DVBCI; + if (time_us) { + rec->presence_flags = WTAP_HAS_TS; + rec->ts.secs = (time_t)(*time_us / (1000 * 1000)); + rec->ts.nsecs = (int)(*time_us % (1000 *1000) * 1000); + } + rec->rec_header.packet_header.caplen = offset; + rec->rec_header.packet_header.len = offset; + + return TRUE; +} + + +static gboolean +camins_read(wtap *wth, wtap_rec *rec, Buffer *buf, int *err, + gchar **err_info, gint64 *data_offset) +{ + *data_offset = file_tell(wth->fh); + + return camins_read_packet(wth->fh, rec, buf, (guint64 *)(wth->priv), + err, err_info); +} + + +static gboolean +camins_seek_read(wtap *wth, gint64 seek_off, wtap_rec *rec, Buffer *buf, + int *err, gchar **err_info) +{ + if (-1 == file_seek(wth->random_fh, seek_off, SEEK_SET, err)) + return FALSE; + + return camins_read_packet(wth->random_fh, rec, buf, NULL, err, err_info); +} + + +wtap_open_return_val camins_open(wtap *wth, int *err, gchar **err_info _U_) +{ + wtap_open_return_val status; + + status = detect_camins_file(wth->fh); + if (status != WTAP_OPEN_MINE) { + /* A read error or a failed heuristic. */ + return status; + } + + /* rewind the fh so we re-read from the beginning */ + if (-1 == file_seek(wth->fh, 0, SEEK_SET, err)) + return WTAP_OPEN_ERROR; + + wth->file_encap = WTAP_ENCAP_DVBCI; + wth->snapshot_length = 0; + wth->file_tsprec = WTAP_TSPREC_USEC; + + /* wth->priv stores a pointer to the global time counter. we update + it as we go through the file sequentially. */ + wth->priv = g_new0(guint64, 1); + + wth->subtype_read = camins_read; + wth->subtype_seek_read = camins_seek_read; + wth->file_type_subtype = camins_file_type_subtype; + + *err = 0; + + /* + * Add an IDB; we don't know how many interfaces were + * involved, so we just say one interface, about which + * we only know the link-layer type, snapshot length, + * and time stamp resolution. + */ + wtap_add_generated_idb(wth); + + return WTAP_OPEN_MINE; +} + +static const struct supported_block_type camins_blocks_supported[] = { + /* + * We support packet blocks, with no comments or other options. + */ + { WTAP_BLOCK_PACKET, MULTIPLE_BLOCKS_SUPPORTED, NO_OPTIONS_SUPPORTED } +}; + +static const struct file_type_subtype_info camins_info = { + "CAM Inspector file", "camins", "camins", NULL, + FALSE, BLOCKS_SUPPORTED(camins_blocks_supported), + NULL, NULL, NULL +}; + +void register_camins(void) +{ + camins_file_type_subtype = wtap_register_file_type_subtype(&camins_info); + + /* + * Register name for backwards compatibility with the + * wtap_filetypes table in Lua. + */ + wtap_register_backwards_compatibility_lua_name("CAMINS", + camins_file_type_subtype); +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ -- cgit v1.2.3