/* dct3trace.c * Routines for reading signalling traces generated by Gammu (www.gammu.org) * from Nokia DCT3 phones in Netmonitor mode. * * gammu --nokiadebug nhm5_587.txt v18-19 * * Duncan Salerno * * Wiretap Library * Copyright (c) 1998 by Gilbert Ramirez * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "config.h" #include "dct3trace.h" #include "wtap-int.h" #include "file_wrappers.h" #include #include #include #include #include #include #include /* Example downlink data: Example uplink data (no raw L1): */ typedef struct dct3trace_file_info { GByteArray* buffer; // holds current chunk of file int64_t start_offset; // where in the file the start of the buffer points } dct3trace_file_info_t; /* We expect to find all the info we need to tell if this file is ours * within this many bytes. Must include the beginTime attribute. */ #define MAGIC_BUF_SIZE 512 /* String constants sought in the XML data. * Written as strings instead of lists of chars for readability. * Use the CLEN() macro to get the length of the constant without counting * the null byte at the end. */ #define CLEN(x) (sizeof(x)-1) /* Magic text to check */ static const unsigned char c_xml_magic[] = ""; static const char dct3trace_magic_record_start[] = "= '0') && (hex <= '9')) return hex - '0'; if ((hex >= 'a') && (hex <= 'f')) return hex - 'a' + 10; return -1; } static int hex2bin(uint8_t *out, uint8_t *out_end, char *in) { uint8_t *out_start = out; int is_low = 0; int c; while (*in != '\0') { c = hc2b(*(unsigned char *)in); if (c < 0) { in++; continue; } if (out == out_end) { /* Too much data */ return -1; } if (is_low == 0) { *out = c << 4; is_low = 1; } else { *out |= (c & 0x0f); is_low = 0; out++; } in++; } return (int)(out - out_start); } static bool xml_get_int(const char* name, xmlChar* str, int* val, int* err, char** err_info) { if (str != NULL) { if (!ws_strtoi32(str, NULL, val)) { *err = WTAP_ERR_BAD_FILE; if (errno == ERANGE) { if (*val < 0) *err_info = ws_strdup_printf("dct3trace: %s value is too small, minimum is %d", name, *val); else *err_info = ws_strdup_printf("dct3trace: %s value is too large, maximum is %d", name, *val); } else *err_info = ws_strdup_printf("dct3trace: %s value \"%s\" not a number", name, str); return false; } } return true; } static bool dct3trace_get_packet(wtap* wth, wtap_rec* rec, const char* text, size_t len, int* err, char** err_info) { xmlDocPtr doc; xmlNodePtr root_element; bool status = true; uint8_t databuf[MAX_PACKET_LEN], * bufp; int local_len = 0; bool have_data = false; doc = xmlParseMemory(text, (int)len); if (doc == NULL) { return false; } root_element = xmlDocGetRootElement(doc); if (root_element == NULL) { ws_debug("empty xml doc"); status = false; goto end; } //Sanity check if (xmlStrcmp(root_element->name, (const xmlChar*)"l1") != 0) { *err = WTAP_ERR_BAD_FILE; *err_info = ws_strdup("dct3trace: Did not start with \"properties; attr; attr = attr->next) { int channel = 0, tmp = 0; if (xmlStrcmp(attr->name, (const xmlChar*)"direction") == 0) { xmlChar* str = xmlNodeListGetString(root_element->doc, attr->children, 1); if (str != NULL) { rec->rec_header.packet_header.pseudo_header.gsm_um.uplink = !strstr(str, "down"); xmlFree(str); } } else if (xmlStrcmp(attr->name, (const xmlChar*)"logicalchannel") == 0) { xmlChar* str = xmlNodeListGetString(root_element->doc, attr->children, 1); status = xml_get_int("logicalchannel", str, &channel, err, err_info); xmlFree(str); if (!status) goto end; switch (channel) { case 128: rec->rec_header.packet_header.pseudo_header.gsm_um.channel = GSM_UM_CHANNEL_SDCCH; break; case 112: rec->rec_header.packet_header.pseudo_header.gsm_um.channel = GSM_UM_CHANNEL_SACCH; break; case 176: rec->rec_header.packet_header.pseudo_header.gsm_um.channel = GSM_UM_CHANNEL_FACCH; break; case 96: rec->rec_header.packet_header.pseudo_header.gsm_um.channel = GSM_UM_CHANNEL_CCCH; break; case 80: rec->rec_header.packet_header.pseudo_header.gsm_um.channel = GSM_UM_CHANNEL_BCCH; break; default: rec->rec_header.packet_header.pseudo_header.gsm_um.channel = GSM_UM_CHANNEL_UNKNOWN; break; } } else if (xmlStrcmp(attr->name, (const xmlChar*)"data") == 0) { have_data = true; /* Found data */ xmlChar* str = xmlNodeListGetString(root_element->doc, attr->children, 1); local_len = hex2bin(bufp, &databuf[MAX_PACKET_LEN], str); xmlFree(str); if (local_len == -1) { *err = WTAP_ERR_BAD_FILE; *err_info = ws_strdup_printf("dct3trace: record length %d too long", rec->rec_header.packet_header.caplen); status = false; goto end; } } else if (!rec->rec_header.packet_header.pseudo_header.gsm_um.uplink) { /* Parse downlink only fields */ if (xmlStrcmp(attr->name, (const xmlChar*)"physicalchannel") == 0) { xmlChar* str = xmlNodeListGetString(root_element->doc, attr->children, 1); status = xml_get_int("physicalchannel", str, &tmp, err, err_info); xmlFree(str); if (!status) goto end; rec->rec_header.packet_header.pseudo_header.gsm_um.arfcn = tmp; } else if (xmlStrcmp(attr->name, (const xmlChar*)"sequence") == 0) { xmlChar* str = xmlNodeListGetString(root_element->doc, attr->children, 1); status = xml_get_int("sequence", str, &tmp, err, err_info); xmlFree(str); if (!status) goto end; rec->rec_header.packet_header.pseudo_header.gsm_um.tdma_frame = tmp; } else if (xmlStrcmp(attr->name, (const xmlChar*)"bsic") == 0) { xmlChar* str = xmlNodeListGetString(root_element->doc, attr->children, 1); status = xml_get_int("bsic", str, &tmp, err, err_info); xmlFree(str); if (!status) goto end; rec->rec_header.packet_header.pseudo_header.gsm_um.bsic = tmp; } else if (xmlStrcmp(attr->name, (const xmlChar*)"error") == 0) { xmlChar* str = xmlNodeListGetString(root_element->doc, attr->children, 1); status = xml_get_int("error", str, &tmp, err, err_info); xmlFree(str); if (!status) goto end; rec->rec_header.packet_header.pseudo_header.gsm_um.error = tmp; } else if (xmlStrcmp(attr->name, (const xmlChar*)"timeshift") == 0) { xmlChar* str = xmlNodeListGetString(root_element->doc, attr->children, 1); status = xml_get_int("timeshift", str, &tmp, err, err_info); xmlFree(str); if (!status) goto end; rec->rec_header.packet_header.pseudo_header.gsm_um.timeshift = tmp; } } } if (!have_data) { for (xmlNodePtr cur = root_element->children; cur != NULL; cur = cur->next) { /* Get data from l2 tag */ if ((cur->type == XML_ELEMENT_NODE) && (xmlStrcmp(cur->name, (const xmlChar*)"l2") == 0)) { for (xmlAttrPtr attr = cur->properties; attr; attr = attr->next) { /* For uplink packets we might not get the raw L1, so have to recreate it from the L2 */ /* Parse L2 header if didn't get data from L1 */ if (xmlStrcmp(attr->name, (const xmlChar*)"data") == 0) { int data_len; have_data = true; /* Found data */ /* * We know we have no data already, so we know * we have enough room for the header. */ switch(rec->rec_header.packet_header.pseudo_header.gsm_um.channel) { case GSM_UM_CHANNEL_SACCH: case GSM_UM_CHANNEL_FACCH: case GSM_UM_CHANNEL_SDCCH: /* Add LAPDm B header */ memset(bufp, 0x1, 2); local_len = 3; break; default: /* Add LAPDm Bbis header */ local_len = 1; break; } bufp += local_len; xmlChar* str = xmlNodeListGetString(cur->doc, attr->children, 1); data_len = hex2bin(bufp, &databuf[MAX_PACKET_LEN], str); xmlFree(str); if (data_len == -1) { *err = WTAP_ERR_BAD_FILE; *err_info = ws_strdup_printf("dct3trace: record length %d too long", rec->rec_header.packet_header.caplen); return false; } local_len += data_len; /* Add LAPDm length byte */ *(bufp - 1) = data_len << 2 | 0x1; } } } } } if (have_data) { /* We've got a full packet! */ wtap_setup_packet_rec(rec, wth->file_encap); rec->block = wtap_block_create(WTAP_BLOCK_PACKET); rec->presence_flags = 0; /* no time stamp, no separate "on the wire" length */ rec->ts.secs = 0; rec->ts.nsecs = 0; rec->rec_header.packet_header.caplen = local_len; rec->rec_header.packet_header.len = local_len; /* Append data to the packet buffer */ ws_buffer_append(&rec->data, databuf, rec->rec_header.packet_header.caplen); } else { /* If not got any data return error */ *err = WTAP_ERR_BAD_FILE; *err_info = g_strdup("dct3trace: record without data"); status = false; } end: xmlFreeDoc(doc); return status; } /* Read from fh and store into buffer, until buffer contains needle. * Returns location of needle once found, or NULL if it's never found * (due to either EOF or read error). */ static uint8_t* read_until(GByteArray* buffer, const unsigned char* needle, FILE_T fh, int* err, char** err_info) { uint8_t read_buffer[RINGBUFFER_CHUNK_SIZE]; uint8_t* found_it; int bytes_read = 0; while (NULL == (found_it = g_strstr_len(buffer->data, buffer->len, needle))) { bytes_read = file_read(read_buffer, RINGBUFFER_CHUNK_SIZE, fh); if (bytes_read < 0) { *err = file_error(fh, err_info); break; } if (bytes_read == 0) { break; } g_byte_array_append(buffer, read_buffer, bytes_read); } return found_it; } /* Find the next packet and parse it; called from wtap_read(). */ static bool dct3trace_read(wtap *wth, wtap_rec *rec, int *err, char **err_info, int64_t *data_offset) { dct3trace_file_info_t* file_info = (dct3trace_file_info_t*)wth->priv; uint8_t* buf_start; uint8_t* msg_start, * msg_end; unsigned msg_offset = 0; size_t msg_len = 0; bool status = false; /* Make sure we have a start and end of message in our buffer -- end first */ msg_end = read_until(file_info->buffer, dct3trace_magic_record_end, wth->fh, err, err_info); if (msg_end == NULL) { goto end; } buf_start = file_info->buffer->data; /* Now search backwards for the message start * (doing it this way should skip over any empty "" tags we have) */ msg_start = g_strrstr_len(buf_start, (unsigned)(msg_end - buf_start), dct3trace_magic_record_start); if (msg_start == NULL || msg_start > msg_end) { *err_info = ws_strdup_printf("dct3trace: Found \"%s\" without matching \"%s\"", dct3trace_magic_record_end, dct3trace_magic_record_start); *err = WTAP_ERR_BAD_FILE; goto end; } /* We know we have a message, what's its offset from the buffer start? */ msg_offset = (unsigned)(msg_start - buf_start); msg_end += CLEN(dct3trace_magic_record_end); msg_len = (unsigned)(msg_end - msg_start); /* Tell Wireshark to put us at the start of the "start_offset + msg_offset; /* pass all of to dct3trace_get_packet() */ status = dct3trace_get_packet(wth, rec, msg_start, msg_len, err, err_info); /* Finally, shift our buffer to the end of this message to get ready for the next one. * Re-use msg_len to get the length of the data we're done with. */ msg_len = msg_end - file_info->buffer->data; while (G_UNLIKELY(msg_len > UINT_MAX)) { g_byte_array_remove_range(file_info->buffer, 0, UINT_MAX); msg_len -= UINT_MAX; } g_byte_array_remove_range(file_info->buffer, 0, (unsigned)msg_len); file_info->start_offset += msg_len; end: if (status == false) { /* There's no more to read. Empty out the buffer */ g_byte_array_set_size(file_info->buffer, 0); } return status; } /* Used to read packets in random-access fashion */ static bool dct3trace_seek_read(wtap *wth, int64_t seek_off, wtap_rec *rec, int *err, char **err_info) { dct3trace_file_info_t* file_info = (dct3trace_file_info_t*)wth->priv; bool status = false; uint8_t* msg_end; unsigned msg_len = 0; /* We stored the offset of the "random_fh, seek_off, SEEK_SET, err) == -1) return false; msg_end = read_until(file_info->buffer, dct3trace_magic_record_end, wth->random_fh, err, err_info); if (msg_end == NULL) { return false; } msg_end += CLEN(dct3trace_magic_record_end); msg_len = (unsigned)(msg_end - file_info->buffer->data); status = dct3trace_get_packet(wth, rec, file_info->buffer->data, msg_len, err, err_info); g_byte_array_set_size(file_info->buffer, 0); return status; } /* Clean up any memory we allocated for dealing with this file. * Set as the subtype_close function in the file_open function below. * (wiretap frees wth->priv itself) */ static void dct3trace_close(wtap* wth) { dct3trace_file_info_t* file_info = (dct3trace_file_info_t*)wth->priv; if (file_info != NULL && file_info->buffer != NULL) { g_byte_array_free(file_info->buffer, true); file_info->buffer = NULL; } } wtap_open_return_val dct3trace_open(wtap* wth, int* err, char** err_info) { char magic_buf[MAGIC_BUF_SIZE + 1]; int bytes_read; const char* curr_pos; dct3trace_file_info_t* file_info; int64_t start_offset; start_offset = file_tell(wth->fh); // Most likely 0 but doesn't hurt to check bytes_read = file_read(magic_buf, MAGIC_BUF_SIZE, wth->fh); if (bytes_read < 0) { *err = file_error(wth->fh, err_info); return WTAP_OPEN_ERROR; } if (bytes_read == 0) { return WTAP_OPEN_NOT_MINE; } if (memcmp(magic_buf, c_xml_magic, CLEN(c_xml_magic)) != 0) { return WTAP_OPEN_NOT_MINE; } curr_pos = g_strstr_len(magic_buf, bytes_read, dct3trace_magic_line2); if (!curr_pos) { return WTAP_OPEN_NOT_MINE; } curr_pos += CLEN(dct3trace_magic_line2); /* Ok it's our file. From here we'll need to free memory */ file_info = g_new0(dct3trace_file_info_t, 1); file_info->start_offset = start_offset + (curr_pos - magic_buf); file_info->buffer = g_byte_array_sized_new(RINGBUFFER_START_SIZE); g_byte_array_append(file_info->buffer, curr_pos, (unsigned)(bytes_read - (curr_pos - magic_buf))); wth->file_type_subtype = dct3trace_file_type_subtype; wth->file_encap = WTAP_ENCAP_GSM_UM; wth->file_tsprec = WTAP_TSPREC_SEC; wth->subtype_read = dct3trace_read; wth->subtype_seek_read = dct3trace_seek_read; wth->subtype_close = dct3trace_close; wth->snapshot_length = 0;/* not known */ wth->priv = (void*)file_info; /* * 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 dct3trace_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 dct3trace_info = { "Gammu DCT3 trace", "dct3trace", "xml", NULL, false, BLOCKS_SUPPORTED(dct3trace_blocks_supported), NULL, NULL, NULL }; void register_dct3trace(void) { dct3trace_file_type_subtype = wtap_register_file_type_subtype(&dct3trace_info); /* * Register name for backwards compatibility with the * wtap_filetypes table in Lua. */ wtap_register_backwards_compatibility_lua_name("DCT3TRACE", dct3trace_file_type_subtype); } /* * Editor modelines - https://www.wireshark.org/tools/modelines.html * * Local variables: * c-basic-offset: 8 * tab-width: 8 * indent-tabs-mode: t * End: * * vi: set shiftwidth=8 tabstop=8 noexpandtab: * :indentSize=8:tabSize=8:noTabs=false: */