/* * PDU Encode * * 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 * */ #include #include #include #include #include #include "global.h" #include "utils.h" #include "pdu_encode.h" #define ENCODE_FAIL(cond, name, ret) \ do { \ if (cond) { \ log_error("encode failed at %s", name); \ return ret; \ } \ } while (0) #define CEIL(X) ((X-(int)(X)) > 0 ? (int)(X+1) : (int)(X)) int pdu_encode_timestamp(char *pdu_str, size_t len, struct tm *tm) { int tmp; int off_upper; int off_lower; int off; int negative; if (len < PDU_TIMESTAMP_SIZE) { log_error("buffer is not large enough to hold a timestamp"); return -1; } tmp = strftime(pdu_str, PDU_TIMESTAMP_SIZE, "%y%m%d%H%M%S", tm); ENCODE_FAIL(tmp != GMT_OFFSET_IDX, "timestamp", -1); off = tm->tm_gmtoff; if (off < 0) { off = -off; negative = 1; } off = off / 60 / 15; off_upper = off / 10; off_lower = off % 10; if (negative) { off_upper |= BIT(3); } snprintf(pdu_str + GMT_OFFSET_IDX, 2, "%X", off_upper & 0xF); snprintf(pdu_str + GMT_OFFSET_IDX + 1, 2, "%X", off_lower & 0xF); strpad(pdu_str, PDU_TIMESTAMP_LEN, 'F'); nibble_swap(pdu_str, PDU_TIMESTAMP_LEN); log_debug("encode_timestamp: %s", pdu_str); return PDU_TIMESTAMP_LEN; } int pdu_encode_addr(char *pdu_str, size_t len, struct pdu_addr *addr, int smsc) { char *begin = pdu_str; int addr_len; int tmp; if (is_telit_3gpp2_format()) { //CDMA length is # of bytes including address type //Ex: Type: 80 #: 9132074666 --> 80 19 23 70 64 66 => 6 bytes addr_len = 1; //1 byte for address type addr_len += CEIL(strlen(addr->addr) / 2); //Number of hex bytes } else { //GSM length is # of digits in converted number //Ex: Type: 81 #: 9132074666 --> 1923706466 => 10 digits addr_len = strlen(addr->addr); } if (smsc) { if (addr_len) { if (addr_len & 1) { addr_len++; } addr_len = addr_len / HEX_BYTE_LEN + 1; } } log_debug("addr-len [transformed]: 0x%02X", addr_len); addr->len = addr_len; tmp = hex_byte_encode(pdu_str, len, addr_len); ENCODE_FAIL(tmp != HEX_BYTE_LEN, "addr-len", -1); pdu_str += tmp; len -= tmp; if (smsc && !addr_len) { log_debug("smsc addr is empty"); goto done; } tmp = hex_byte_encode(pdu_str, len, addr->type); ENCODE_FAIL(tmp != HEX_BYTE_LEN, "addr-type", -1); pdu_str += tmp; len -= tmp; addr_len = strlen(addr->addr); if (addr_len & 1) { addr_len++; } if (len < addr_len + 1) { log_error("buffer is not large enough to hold the addr"); return -1; } strcpy(pdu_str, addr->addr); strpad(pdu_str, addr_len, 'F'); nibble_swap(pdu_str, addr_len); len -= addr_len; pdu_str += addr_len; done: return pdu_str - begin; } #define encode_octet(buf, n) \ shiftr(bit_maskl(cycledown(n, 7) + 1, 7) & buf[septet_idx(n)], cycleup(n, 7)) | \ shiftl(bit_maskr(cycleup(n, 7) + 1) & buf[septet_idx(n) + 1], cycledown(n, 7) + 1) #define encode_cdma_octet(buf, n) \ shiftl(buf[septet_idx(n)], cycleup(n, 7) + 1) | shiftr(buf[septet_idx(n) + 1], cycledown(n, 7)) int pdu_encode_user_data(char *pdu_str, size_t len, struct pdu_info *pdu, int *nr_octets) { char *begin = pdu_str; int tmp; uint8_t octets[PDU_OCTETS_MAX]; int i; 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) { log_debug("data coding alphabet is default (7-bit)"); } 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 { log_debug("data coding alphabet 0x%02X not implemented", pdu->data_coding.general.alphabet); return -1; } // --------------------------------------------------------------------- // FOR GSM 7-BIT ENCODING ONLY, ENCODE FROM IRA ALPHABET TO GSM ALPHABET // --------------------------------------------------------------------- if (pdu->data_coding.general.alphabet == PDU_ALPHABET_DEFAULT) { int read = 0; int store = 0; char initial_user_data[PDU_UD_7BIT_MAX]; for (read = 0; read < pdu->user_data_len; read++) { initial_user_data[read] = pdu->user_data[read]; } initial_user_data[read] = 0; log_debug("Converting from IRA character set to GSM"); for (read = 0; read < pdu->user_data_len; read++) { int GSMchar = indexOfChar(strGSMTable, GSM_TABLE_SIZE, initial_user_data[read]); if (GSMchar >= 0) { log_debug("IRA before: 0x%02X | GSM after: 0x%02X", initial_user_data[read], GSMchar); pdu->user_data[store] = GSMchar; } else { //Check in extended table GSMchar = indexOfChar(strExtendedTable, GSM_TABLE_SIZE, initial_user_data[read]); if (GSMchar >= 0) { log_debug("IRA before: 0x%02X | GSM after: 0x1B%02X", initial_user_data[read], GSMchar); pdu->user_data[store] = 0x1B; store++; pdu->user_data[store] = GSMchar; } else { log_debug("IRA before: 0x%02X | Translation not found - no change", initial_user_data[read]); pdu->user_data[store] = initial_user_data[read]; } } store++; } //Update lengths pdu->user_data_len += store - read; } // ------------------------------ // ENCODE USER DATA INTO PDU DATA // ------------------------------ // GSM 7-BIT ENCODING if (pdu->data_coding.general.alphabet == PDU_ALPHABET_DEFAULT) { if (pdu->user_data_len > PDU_UD_7BIT_MAX) { log_error("string exceeds 7-bit data max length"); return -1; } *nr_octets = octets_from_septets(pdu->user_data_len); for (i = 0; i < *nr_octets; i++) { octets[i] = encode_octet(pdu->user_data, i); log_debug("ENCODE: i: %d data: 0x%02X --> octet: 0x%02X", i, pdu->user_data[i], octets[i]); } // CDMA 7-BIT ENCODING } else if (pdu->data_coding.general.alphabet == PDU_ALPHABET_CDMA_DEFAULT) { if (pdu->user_data_len > PDU_UD_7BIT_MAX) { log_error("string exceeds 7-bit data max length"); return -1; } *nr_octets = octets_from_septets(pdu->user_data_len); for (i = 0; i < *nr_octets; i++) { octets[i] = encode_cdma_octet(pdu->user_data, i); log_debug("ENCODE: i: %d data: 0x%02X --> octet: 0x%02X", i, pdu->user_data[i], octets[i]); } // ALL 8-BIT ENCODING } else { if (pdu->user_data_len > PDU_UD_8BIT_MAX) { log_error("string exceeds 8-bit data max length"); return -1; } *nr_octets = pdu->user_data_len; for (i = 0; i < pdu->user_data_len; i++) { octets[i] = pdu->user_data[i]; } } if (len < *nr_octets * 2 + HEX_BYTE_LEN + 1) { log_error("buffer is not large enough to hold user-data"); return -1; } if ((pdu->data_coding.general.alphabet == PDU_ALPHABET_CDMA_DEFAULT) && !strcmp(Global.core.model, "DE910-DUAL")) { // user_data_len is stored as number of encoded octets log_debug("putting nr_octets into PDU as data length (DE910-DUAL)"); tmp = hex_byte_encode(pdu_str, len, *nr_octets); } else { // user_data_len is stored as number of septets log_debug("putting user_data_len into PDU as data length"); tmp = hex_byte_encode(pdu_str, len, pdu->user_data_len); } ENCODE_FAIL(tmp != HEX_BYTE_LEN, "user-data-len", -1); pdu_str += tmp; len -= tmp; for (i = 0; i < *nr_octets; i++) { hex_byte_encode(pdu_str, len, octets[i]); pdu_str += HEX_BYTE_LEN; } len -= *nr_octets * 2; pdu_str[i] = '\0'; log_debug("user-data-len: 0x%02X (before encoding)", pdu->user_data_len); log_debug("nr_octets: 0x%02X (after encoding)", *nr_octets); return pdu_str - begin; } int pdu_encode(char *pdu_str, size_t len, struct pdu_info *pdu) { char *begin = pdu_str; char *msg_begin = pdu_str; int tmp; int nr_octets; tmp = pdu_encode_addr(pdu_str, len, &pdu->smsc_addr, 1); ENCODE_FAIL(tmp < 0, "smsc-addr", -1); len -= tmp; pdu_str += tmp; msg_begin = pdu_str; tmp = hex_byte_encode(pdu_str, len, pdu->type.type); ENCODE_FAIL(tmp != HEX_BYTE_LEN, "type", -1); len -= tmp; pdu_str += tmp; if (pdu->type.msg_type == PDU_MTI_SUBMIT || pdu->type.msg_type == PDU_MTI_REPORT) { tmp = hex_byte_encode(pdu_str, len, pdu->msg_reference); ENCODE_FAIL(tmp != HEX_BYTE_LEN, "msg-reference", -1); len -= tmp; pdu_str += tmp; } tmp = pdu_encode_addr(pdu_str, len, &pdu->addr, 0); ENCODE_FAIL(tmp < 0, "addr", -1); len -= tmp; pdu_str += tmp; switch (pdu->type.msg_type) { case PDU_MTI_REPORT: tmp = pdu_encode_timestamp(pdu_str, len, &pdu->timestamp); ENCODE_FAIL(tmp < 0, "report-timestamp", -1); len -= tmp; pdu_str += tmp; break; case PDU_MTI_DELIVER: case PDU_MTI_SUBMIT: tmp = hex_byte_encode(pdu_str, len, pdu->protocol_id); ENCODE_FAIL(tmp != HEX_BYTE_LEN, "protocol-id", -1); len -= tmp; pdu_str += tmp; tmp = hex_byte_encode(pdu_str, len, pdu->data_coding.data_coding); ENCODE_FAIL(tmp != HEX_BYTE_LEN, "data-coding-scheme", -1); len -= tmp; pdu_str += tmp; if (pdu->type.msg_type == PDU_MTI_SUBMIT) { switch (pdu->type.validity_period_format) { case PDU_VPF_RELATIVE: tmp = hex_byte_encode(pdu_str, len, pdu->validity_period); ENCODE_FAIL(tmp != HEX_BYTE_LEN, "validity-period", -1); len -= tmp; pdu_str += tmp; break; case PDU_VPF_ENHANCED: log_warning("PDU_VPF_ENHANCED? Falling through to absolute"); case PDU_VPF_ABSOUTE: tmp = pdu_encode_timestamp(pdu_str, len, &pdu->timestamp); ENCODE_FAIL(tmp < 0, "validity-period-timestamp", -1); len -= tmp; pdu_str += tmp; break; case PDU_VPF_NOT_PRESENT: default: break; } } else if (pdu->type.msg_type == PDU_MTI_DELIVER) { tmp = pdu_encode_timestamp(pdu_str, len, &pdu->timestamp); ENCODE_FAIL(tmp < 0, "delivery-timestamp", -1); len -= tmp; pdu_str += tmp; } tmp = pdu_encode_user_data(pdu_str, len, pdu, &nr_octets); ENCODE_FAIL(tmp < 0, "user-data", -1); len -= tmp; 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_encode_cdma(char *pdu_str, size_t len, struct pdu_info *pdu) { char *begin = pdu_str; char *msg_begin = pdu_str; int tmp; int nr_octets; //Set pdu encoding for CDMA pdu->data_coding.general.alphabet = PDU_ALPHABET_CDMA_DEFAULT; //Destination address tmp = pdu_encode_addr(pdu_str, len, &pdu->addr, 0); ENCODE_FAIL(tmp < 0, "addr", -1); len -= tmp; pdu_str += tmp; //Callback address (Always 00 because it's empty) tmp = hex_byte_encode(pdu_str, len, 0); ENCODE_FAIL(tmp != HEX_BYTE_LEN, "callback-addr-len", -1); pdu_str += tmp; len -= tmp; //Teleservice ID (always 1002) tmp = hex_byte_encode(pdu_str, len, 16); ENCODE_FAIL(tmp != HEX_BYTE_LEN, "tele-id-1", -1); pdu_str += tmp; len -= tmp; tmp = hex_byte_encode(pdu_str, len, 2); ENCODE_FAIL(tmp != HEX_BYTE_LEN, "tele-id-2", -1); pdu_str += tmp; len -= tmp; //Priority (always 00 for normal) tmp = hex_byte_encode(pdu_str, len, 0); ENCODE_FAIL(tmp != HEX_BYTE_LEN, "priority", -1); pdu_str += tmp; len -= tmp; switch (pdu->type.msg_type) { case PDU_MTI_REPORT: log_error("msg_type PDU_MTI_REPORT not supported!"); return 0; case PDU_MTI_DELIVER: log_debug("msg_type: PDU_MTI_DELIVER"); case PDU_MTI_SUBMIT: log_debug("msg_type: PDU_MTI_SUBMIT"); //add data coding scheme tmp = hex_byte_encode(pdu_str, len, pdu->data_coding.data_coding); ENCODE_FAIL(tmp != HEX_BYTE_LEN, "data-coding-scheme", -1); len -= tmp; pdu_str += tmp; tmp = pdu_encode_user_data(pdu_str, len, pdu, &nr_octets); ENCODE_FAIL(tmp < 0, "user-data", -1); len -= tmp; pdu_str += tmp; } // pdu->msg_len = (pdu_str - msg_begin) / 2; pdu->msg_len = 6 + nr_octets; log_debug("msg-len: %d", pdu->msg_len); return pdu_str - begin; }