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