/* * PDU Decode * * Copyright (C) 2010 by James Maki * * Author: James Maki * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #define _GNU_SOURCE #include #include #include #include #include #include "config.h" #include "global.h" #include "log.h" #include "utils.h" #include "pdu_decode.h" #define DECODE_FAIL(cond, name, ret) \ do { \ if (cond) { \ log_error("decode failed at %s", name); \ return ret; \ } \ } while (0) int pdu_decode_timestamp(const char *pdu_str, struct tm *tm) { char buf[PDU_TIMESTAMP_SIZE + 3]; char *cp; int off_upper; int off_lower; int off; STRLEN_CHECK(pdu_str, PDU_TIMESTAMP_LEN, -1); memset(tm, 0, sizeof(*tm)); memset(buf, 0, sizeof(buf)); strncpy(buf, pdu_str, PDU_TIMESTAMP_LEN); nibble_swap(buf, PDU_TIMESTAMP_LEN); strunpad(buf, 'F'); off_upper = hex_nibble_scan(buf + GMT_OFFSET_IDX, 1); off_lower = hex_nibble_scan(buf + GMT_OFFSET_IDX + 1, 1); if (off_upper & BIT(3)) { off_upper &= ~BIT(3); buf[GMT_OFFSET_IDX] = '-'; } else { buf[GMT_OFFSET_IDX] = '+'; } off = (off_upper * 10 + off_lower) * 15; snprintf(buf + GMT_OFFSET_IDX + 1, 5, "%02d%02d", off / 60, off % 60); cp = (char *) strptime(buf, "%y%m%d%H%M%S%z", tm); if (!cp || *cp) { log_error("timestamp could not be converted to tm"); return -1; } return PDU_TIMESTAMP_LEN; } int pdu_decode_cdma_timestamp(const char *pdu_str, struct tm *tm) { char buf[PDU_CDMA_TIMESTAMP_LEN + 1]; char *cp; STRLEN_CHECK(pdu_str, PDU_CDMA_TIMESTAMP_LEN, -1); memset(tm, 0, sizeof(*tm)); memset(buf, 0, sizeof(buf)); strncpy(buf, pdu_str, PDU_CDMA_TIMESTAMP_LEN); cp = (char *) strptime(buf, "%y%m%d%H%M%S", tm); if (!cp || *cp) { log_error("timestamp could not be converted to tm"); return -1; } return PDU_CDMA_TIMESTAMP_LEN; } int pdu_decode_addr(const char *pdu_str, struct pdu_addr *addr, int smsc) { const char *begin = pdu_str; int addr_len; int tmp; memset(addr, 0, sizeof(*addr)); STRLEN_CHECK(pdu_str, HEX_BYTE_LEN, -1); tmp = hex_byte_decode(pdu_str); DECODE_FAIL(tmp < 0, "addr-len", -1); pdu_str += HEX_BYTE_LEN; addr->len = tmp; addr_len = addr->len; if (smsc) { if (!addr_len) { goto done; } addr_len = addr_len * HEX_BYTE_LEN - HEX_BYTE_LEN; } else { if (addr_len & 1) { addr_len++; } } STRLEN_CHECK(pdu_str, HEX_BYTE_LEN, -1); tmp = hex_byte_decode(pdu_str); DECODE_FAIL(tmp < 0, "addr-type", -1); pdu_str += HEX_BYTE_LEN; addr->type = tmp; if (addr_len < 0 || addr_len >= sizeof(addr->addr)) { log_error("invalid length: 0x%02X", addr_len); return -1; } log_debug("addr-len [transformed]: 0x%02X", addr_len); log_debug("addr-type: 0x%02X", addr->type); STRLEN_CHECK(pdu_str, addr_len, -1); strncpy(addr->addr, pdu_str, addr_len); nibble_swap(addr->addr, addr_len); strunpad(addr->addr, 'F'); if (addr->type == 0x91) {//91 indicates international format of the phone number memmove(&addr->addr[1],addr->addr,addr_len); addr->addr[0]='+'; }; pdu_str += addr_len; done: log_debug("addr: %s", addr->addr); log_debug("addr-len: 0x%02X", addr->len); return pdu_str - begin; } #define decode_septet_from_octet(buf, n) \ shiftl(bit_maskr(cycledown(n, 8)) & buf[octet_idx(n)], cycleup(n, 8)) | \ shiftr(bit_maskl(cycleup(n, 8), 8) & buf[octet_idx(n - 1)], cycledown(n - 1, 8)) #define decode_cdma_septet_from_octet(buf, n) \ shiftr(buf[octet_idx(n)], cycleup(n, 8) + 1) | \ shiftl(buf[octet_idx(n) - 1], cycledown(n, 8)) & 0x7F int pdu_decode_user_data(const char *pdu_str, struct pdu_info *pdu, int *nr_octets) { const char *begin = pdu_str; int tmp; uint8_t octets[PDU_OCTETS_MAX + 1]; int user_data_start_index = 0; int i; STRLEN_CHECK(pdu_str, HEX_BYTE_LEN, -1); tmp = hex_byte_decode(pdu_str); DECODE_FAIL(tmp < 0, "user-data-len", -1); pdu_str += HEX_BYTE_LEN; pdu->user_data_len = tmp; if (pdu->data_coding.general.unused) { log_error("data coding group 0x%02X not implemented", pdu->data_coding.data_coding & 0xF0); return -1; } if (pdu->data_coding.general.alphabet == PDU_ALPHABET_DEFAULT || pdu->data_coding.general.alphabet == PDU_ALPHABET_DEFAULT2 ) { log_debug("data coding alphabet is default (7-bit)"); } else if (pdu->data_coding.general.alphabet == PDU_ALPHABET_DEFAULT_MULTI) { log_debug("data coding alphabet is default (7-bit) multi-part"); } else if (pdu->data_coding.general.alphabet == PDU_ALPHABET_CDMA_DEFAULT) { log_debug("data coding alphabet is CDMA default (7-bit)"); } else if (pdu->data_coding.general.alphabet == PDU_ALPHABET_EIGHT) { log_debug("data coding alphabet is eight"); } else if (pdu->data_coding.general.alphabet == PDU_ALPHABET_CDMA_EIGHT) { log_debug("data coding alphabet is CDMA eight"); } else if (pdu->data_coding.general.alphabet == PDU_ALPHABET_TE) { log_debug("data coding alphabet is TE specific 7-bit"); } else { log_debug("data coding alphabet 0x%02X not implemented", pdu->data_coding.general.alphabet); return -1; } // ----------------------------------------------------------------------------- // VERIFY DATA LENGTH AND GET NUMBER OF OCTETS (ACTUAL DATA BYTES IN PDU STRING) // ----------------------------------------------------------------------------- if ((pdu->data_coding.general.alphabet == PDU_ALPHABET_DEFAULT) || (pdu->data_coding.general.alphabet == PDU_ALPHABET_DEFAULT2) || (pdu->data_coding.general.alphabet == PDU_ALPHABET_DEFAULT_MULTI)|| (pdu->data_coding.general.alphabet == PDU_ALPHABET_TE)) { if (pdu->user_data_len > PDU_UD_7BIT_MAX) { log_warning("pdu contains invalid user-data-len: 0x%02X", pdu->user_data_len); pdu->user_data_len = PDU_UD_7BIT_MAX; } //GSM 7-bit data (septets) length is encoded in PDU as number of septets // (which is same as user_data_len), but octets is needed for decoding loop *nr_octets = octets_from_septets(pdu->user_data_len); // CDMA 7-BIT ENCODING } else if (pdu->data_coding.general.alphabet == PDU_ALPHABET_CDMA_DEFAULT) { if (is_telit_lte_vzw_3gpp2_format()) { // LE910-SVG stores data length as # of septets log_debug("counting PDU length byte as number of septets (LE910-SVG/LE910-SV1/LE910-NA1/VZW)"); *nr_octets = octets_from_septets(pdu->user_data_len); } else { // Other CDMA radios store data length as # of octets // NOTE: CE910 send PDU needs # of septets but receive PDU is in # of octets log_debug("counting PDU length byte as number of octets"); *nr_octets = pdu->user_data_len; pdu->user_data_len = septets_from_octets(pdu->user_data_len); } if (pdu->user_data_len > PDU_UD_7BIT_MAX) { log_warning("pdu contains invalid user-data-len: 0x%02X", pdu->user_data_len); pdu->user_data_len = PDU_UD_7BIT_MAX; } // GSM & CDMA 8-BIT ENCODING } else if ((pdu->data_coding.general.alphabet == PDU_ALPHABET_EIGHT) || (pdu->data_coding.general.alphabet == PDU_ALPHABET_CDMA_EIGHT)) { if (pdu->user_data_len > PDU_UD_8BIT_MAX) { log_warning("pdu contains invalid user-data-len: 0x%02X", pdu->user_data_len); pdu->user_data_len = PDU_UD_8BIT_MAX; } //Original data is octets (all 8 bits could contain data) //Therefore, octets = user_data_len *nr_octets = pdu->user_data_len; } STRLEN_CHECK(pdu_str, *nr_octets * 2, -1); log_debug("nr_octets: 0x%02X (encoded data length)", *nr_octets); log_debug("user-data-len: 0x%02X (decoded data length)", pdu->user_data_len); //Copy user data from pdu string into octets for (i = 0; i < *nr_octets; i++) { octets[i] = hex_byte_decode(pdu_str); pdu_str += HEX_BYTE_LEN; } // --------------------------------------------------------- // DECODE USER DATA (IF NEEDED) AND COPY INTO pdu->user_data // --------------------------------------------------------- // GSM 7-BIT & LVW2 7-BIT MULTI-PART if ((pdu->data_coding.general.alphabet == PDU_ALPHABET_DEFAULT) || (pdu->data_coding.general.alphabet == PDU_ALPHABET_DEFAULT2) || (pdu->data_coding.general.alphabet == PDU_ALPHABET_DEFAULT_MULTI)|| (pdu->data_coding.general.alphabet == PDU_ALPHABET_TE)) { // Keep UDH for concatenated SMS ONLY. // Otherwise it is impossible to process correctly concatenated SMS. i = 0; if (pdu->type.user_data_header || // For LVW3, LNA3 and LVW2 - Save header ONLY when it's actually a multi-part message. // Multi-part messages should use Teleservice ID 4101 (0x1005) in LVW2 7-BIT MULTI-PART format. // See 3GPP2 X.S0004-550-E for details on Teleservice ID. (pdu->data_coding.general.alphabet == PDU_ALPHABET_DEFAULT_MULTI && pdu->teleservice_id == PDU_TELE_ID_CDMA_WEMT)) { for (i = 0; i <= octets[0]; ++i) { pdu->user_data[i] = octets[i]; } i = octets[0] + 1; // Set message text start after UDH. // Process octets padding for 7-bit encoding. if (0 != (i * 8) % 7) { pdu->user_data[i] = 0; // set padding data to 0; i++; } } user_data_start_index = i; for (; i < pdu->user_data_len; i++) { pdu->user_data[i] = decode_septet_from_octet(octets, i); log_debug("DECODE: i: %d octet: 0x%02X --> data: 0x%02X", i, octets[i], pdu->user_data[i]); } pdu->user_data[i] = '\0'; // CDMA 7-BIT } else if (pdu->data_coding.general.alphabet == PDU_ALPHABET_CDMA_DEFAULT) { // Keep UDH for concatenated SMS. // Otherwise it is impossible to process correctly concatenated SMS. i = 0; if (pdu->type.user_data_header) { for (i = 0; i <= octets[0]; ++i) { pdu->user_data[i] = octets[i]; } i = octets[0] + 1; // Set message text start after UDH. // Process octets padding for 7-bit encoding. if (0 != (i * 8) % 7) { pdu->user_data[i] = 0; // set padding data to 0; i++; } } for (; i < pdu->user_data_len; i++) { pdu->user_data[i] = decode_cdma_septet_from_octet(octets, i); log_debug("DECODE: i: %d octet: 0x%02X --> data: 0x%02X", i, octets[i], pdu->user_data[i]); } //Remove padded byte for data length of 7 characters if not LE910-SVG if (!is_telit_lte_vzw_3gpp2_format() && (pdu->user_data[i-1] == 0)) { log_debug("Removing padded char"); i--; pdu->user_data_len--; } pdu->user_data[i] = '\0'; // ALL 8-BIT ENCODING } else { for (i = 0; i < pdu->user_data_len; i++) { pdu->user_data[i] = octets[i]; } pdu->user_data[i] = '\0'; } // --------------------------------------------------------------------- // FOR GSM 7-BIT ENCODING ONLY, DECODE FROM GSM ALPHABET TO IRA ALPHABET // (includes LVW2 multi-part) // --------------------------------------------------------------------- if ((pdu->data_coding.general.alphabet == PDU_ALPHABET_DEFAULT) || (pdu->data_coding.general.alphabet == PDU_ALPHABET_DEFAULT2) || (pdu->data_coding.general.alphabet == PDU_ALPHABET_DEFAULT_MULTI)) { int read = user_data_start_index; int store = user_data_start_index; log_debug("Converting from GSM character set to IRA"); for (; read < pdu->user_data_len; read++) { if (pdu->user_data[read] == 0x1B) { //Character from the extended set using the escape char (27) read++; log_debug("GSM before: 0x1B%02X | IRA after: 0x%02X", pdu->user_data[read], strExtendedTable[pdu->user_data[read]]); pdu->user_data[store] = strExtendedTable[pdu->user_data[read]]; } else { log_debug("GSM before: 0x%02X | IRA after: 0x%02X", pdu->user_data[read], strGSMTable[pdu->user_data[read]]); pdu->user_data[store] = strGSMTable[pdu->user_data[read]]; } store++; } //Update lengths pdu->user_data_len -= read - store; pdu_str -= (read - store) * HEX_BYTE_LEN; } return pdu_str - begin; } int pdu_decode(const char *pdu_str, struct pdu_info *pdu) { const char *begin = pdu_str; const char *msg_begin; int tmp; int nr_octets; memset(pdu, 0, sizeof(*pdu)); tmp = pdu_decode_addr(pdu_str, &pdu->smsc_addr, 1); DECODE_FAIL(tmp < 0, "smsc-addr", -1); pdu_str += tmp; msg_begin = pdu_str; log_debug("smsc-addr: %s", pdu->smsc_addr.addr); log_debug("smsc-addr-type: 0x%02X", pdu->smsc_addr.type); log_debug("smsc-addr-len: 0x%02X", pdu->smsc_addr.len); STRLEN_CHECK(pdu_str, HEX_BYTE_LEN, -1); tmp = hex_byte_decode(pdu_str); DECODE_FAIL(tmp < 0, "type", -1); pdu_str += HEX_BYTE_LEN; pdu->type.type = tmp; log_debug("type: 0x%02X", pdu->type.type); if (pdu->type.msg_type == PDU_MTI_SUBMIT || pdu->type.msg_type == PDU_MTI_REPORT) { STRLEN_CHECK(pdu_str, HEX_BYTE_LEN, -1); tmp = hex_byte_decode(pdu_str); DECODE_FAIL(tmp < 0, "msg-reference", -1); pdu_str += HEX_BYTE_LEN; pdu->msg_reference = tmp; log_debug("msg-reference: 0x%02X", pdu->msg_reference); } tmp = pdu_decode_addr(pdu_str, &pdu->addr, 0); DECODE_FAIL(tmp < 0, "addr", -1); pdu_str += tmp; log_debug("addr: %s", pdu->addr.addr); log_debug("addr-type: 0x%02X", pdu->addr.type); log_debug("addr-len: 0x%02X", pdu->addr.len); switch (pdu->type.msg_type) { case PDU_MTI_REPORT: tmp = pdu_decode_timestamp(pdu_str, &pdu->timestamp); DECODE_FAIL(tmp < 0, "report-timestamp", -1); pdu_str += tmp; break; case PDU_MTI_DELIVER: case PDU_MTI_SUBMIT: STRLEN_CHECK(pdu_str, HEX_BYTE_LEN, -1); tmp = hex_byte_decode(pdu_str); DECODE_FAIL(tmp < 0, "protocol-id", -1); pdu_str += HEX_BYTE_LEN; pdu->protocol_id = tmp; log_debug("protocol-id: 0x%02X", pdu->protocol_id); STRLEN_CHECK(pdu_str, HEX_BYTE_LEN, -1); tmp = hex_byte_decode(pdu_str); DECODE_FAIL(tmp < 0, "data-coding-scheme", -1); pdu_str += HEX_BYTE_LEN; pdu->data_coding.data_coding = tmp; log_debug("data-coding-scheme: 0x%02X", pdu->data_coding.data_coding); if (pdu->type.msg_type == PDU_MTI_SUBMIT) { log_debug("validity-period-format: 0x%02X", pdu->type.validity_period_format); switch (pdu->type.validity_period_format) { case PDU_VPF_RELATIVE: STRLEN_CHECK(pdu_str, HEX_BYTE_LEN, -1); tmp = hex_byte_decode(pdu_str); DECODE_FAIL(tmp < 0, "validity-period", -1); pdu_str += HEX_BYTE_LEN; pdu->validity_period = tmp; log_debug("validity-period: 0x%02X", pdu->validity_period); break; case PDU_VPF_ENHANCED: log_warning("PDU_VPF_ENHANCED? Falling through to absolute"); case PDU_VPF_ABSOUTE: tmp = pdu_decode_timestamp(pdu_str, &pdu->timestamp); DECODE_FAIL(tmp < 0, "validity-period-timestamp", -1); pdu_str += tmp; return -1; case PDU_VPF_NOT_PRESENT: default: break; } } else if (pdu->type.msg_type == PDU_MTI_DELIVER) { tmp = pdu_decode_timestamp(pdu_str, &pdu->timestamp); DECODE_FAIL(tmp < 0, "delivery-timestamp", -1); pdu_str += tmp; } tmp = pdu_decode_user_data(pdu_str, pdu, &nr_octets); DECODE_FAIL(tmp < 0, "user-data", -1); pdu_str += tmp; } pdu->msg_len = (pdu_str - msg_begin) / 2; log_debug("msg_len: %d", pdu->msg_len); return pdu_str - begin; } int pdu_decode_cdma(const char *pdu_str, struct pdu_info *pdu) { const char *begin = pdu_str; const char *msg_begin; int tmp; int nr_octets; memset(pdu, 0, sizeof(*pdu)); //Destination address tmp = pdu_decode_addr(pdu_str, &pdu->addr, 1); DECODE_FAIL(tmp < 0, "addr", -1); pdu_str += tmp; msg_begin = pdu_str; STRLEN_CHECK(pdu_str, HEX_BYTE_LEN, -1); //Timestamp tmp = pdu_decode_cdma_timestamp(pdu_str, &pdu->timestamp); DECODE_FAIL(tmp < 0, "timestamp", -1); pdu_str += tmp; //Teleservice ID tmp = hex_byte_decode(pdu_str); DECODE_FAIL(tmp < 0, "tele-id-1", -1); pdu_str += HEX_BYTE_LEN; log_debug("tele-id-1: 0x%02X", tmp); pdu->teleservice_id = 0; pdu->teleservice_id |= (tmp << 8); // save upper byte of the Teleservice ID tmp = hex_byte_decode(pdu_str); DECODE_FAIL(tmp < 0, "tele-id-2", -1); pdu_str += HEX_BYTE_LEN; log_debug("tele-id-2: 0x%02X", tmp); pdu->teleservice_id |= (tmp); // save lower byte of the Teleservice ID log_debug("tele-id: 0x%04X", pdu->teleservice_id); //Priority tmp = hex_byte_decode(pdu_str); DECODE_FAIL(tmp < 0, "priority", -1); pdu_str += HEX_BYTE_LEN; log_debug("priority: 0x%02X", tmp); //Data coding scheme tmp = hex_byte_decode(pdu_str); DECODE_FAIL(tmp < 0, "data-coding-scheme", -1); pdu_str += HEX_BYTE_LEN; pdu->data_coding.data_coding = tmp; log_debug("data-coding-scheme: 0x%02X", pdu->data_coding.data_coding); //User Data tmp = pdu_decode_user_data(pdu_str, pdu, &nr_octets); DECODE_FAIL(tmp < 0, "user-data", -1); pdu_str += tmp; pdu->msg_len = 6 + nr_octets; log_debug("msg_len: %d", pdu->msg_len); return pdu_str - begin; }