/* * Copyright (c) 2017, Arm Limited and affiliates. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include "mbed_wait_api.h" #include "AT_CellularSMS.h" #include "CellularUtil.h" #include "CellularLog.h" using namespace mbed_cellular_util; using namespace mbed; using namespace std; #define CTRL_Z "\x1a" #define ESC "\x1b" const uint8_t SMS_STATUS_SIZE = 12 + 1; const uint8_t FIRST_OCTET_DELIVER_SUBMIT = 17; const uint8_t TP_VALIDITY_PERIOD_24_HOURS = 167; const uint8_t TP_PROTOCOL_IDENTIFIER = 0; const uint8_t SMS_DATA_CODING_SCHEME = 0; const uint8_t SMS_MAX_8BIT_CONCATENATED_SINGLE_SMS_SIZE = 134; const uint8_t SMS_MAX_GSM7_CONCATENATED_SINGLE_SMS_SIZE = 153; #define NVAM '?' // Not Valid ascii, ISO-8859-1 mark // mapping table from 7-bit GSM to ascii (ISO-8859-1) static const int gsm_to_ascii[] = { 64, // 0 163, // 1 36, // 2 165, // 3 232, // 4 233, // 5 249, // 6 236, // 7 242, // 8 199, // 9 10, // 10 216, // 11 248, // 12 13, // 13 197, // 14 229, // 15 NVAM, // 16 95, // 17 NVAM, // 18 NVAM, // 19 NVAM, // 20 NVAM, // 21 NVAM, // 22 NVAM, // 23 NVAM, // 24 NVAM, // 25 NVAM, // 26 27, // 27 198, // 28 230, // 29 223, // 30 201, // 31 32, // 32 33, // 33 34, // 34 35, // 35 164, // 36 37, // 37 38, // 38 39, // 39 40, // 40 41, // 41 42, // 42 43, // 43 44, // 44 45, // 45 46, // 46 47, // 47 48, // 48 49, // 49 50, // 50 51, // 51 52, // 52 53, // 53 54, // 54 55, // 55 56, // 56 57, // 57 58, // 58 59, // 59 60, // 60 61, // 61 62, // 62 63, // 63 161, // 64 65, // 65 66, // 66 67, // 67 68, // 68 69, // 69 70, // 70 71, // 71 72, // 72 73, // 73 74, // 74 75, // 75 76, // 76 77, // 77 78, // 78 79, // 79 80, // 80 81, // 81 82, // 82 83, // 83 84, // 84 85, // 85 86, // 86 87, // 87 88, // 88 89, // 89 90, // 90 196, // 91 214, // 92 209, // 93 220, // 94 167, // 95 191, // 96 97, // 97 98, // 98 99, // 99 100, // 100 101, // 101 102, // 102 103, // 103 104, // 104 105, // 105 106, // 106 107, // 107 108, // 108 109, // 109 110, // 110 111, // 111 112, // 112 113, // 113 114, // 114 115, // 115 116, // 116 117, // 117 118, // 118 119, // 119 120, // 120 121, // 121 122, // 122 228, // 123 246, // 124 241, // 125 252, // 126 224 // 127 }; const int GSM_TO_ASCII_TABLE_SIZE = sizeof(gsm_to_ascii)/sizeof(gsm_to_ascii[0]); AT_CellularSMS::AT_CellularSMS(ATHandler &at) : AT_CellularBase(at), _cb(0), _mode(CellularSMSMmodeText), _use_8bit_encoding(false), _sim_wait_time(0), _sms_message_ref_number(1), _sms_info(NULL) { /* URCs, handled out of band */ _at.set_urc_handler("+CMTI:", callback(this, &AT_CellularSMS::cmti_urc)); _at.set_urc_handler("+CMT:", callback(this, &AT_CellularSMS::cmt_urc)); } AT_CellularSMS::~AT_CellularSMS() { } void AT_CellularSMS::cmt_urc() { tr_debug("CMT_URC called"); //+CMT: ,[],[,,,,,,,] _at.consume_to_stop_tag(); // call user defined callback function if (_cb) { _cb(); } else { tr_warn("cmt_urc, no user defined callback for receiving sms!"); } } void AT_CellularSMS::cmti_urc() { //+CMTI: ,, tr_debug("CMTI_URC called"); // call user defined callback function if (_cb) { _cb(); } else { tr_warn("cmti_urc, no user defined callback for receiving sms!"); } } nsapi_error_t AT_CellularSMS::set_cnmi() { _at.lock(); _at.cmd_start("AT+CNMI=2,1"); _at.cmd_stop(); _at.resp_start(); _at.resp_stop(); return _at.unlock_return_error(); } nsapi_error_t AT_CellularSMS::set_cmgf(int msg_format) { _at.lock(); _at.cmd_start("AT+CMGF="); _at.write_int(msg_format); _at.cmd_stop(); _at.resp_start(); _at.resp_stop(); return _at.unlock_return_error(); } nsapi_error_t AT_CellularSMS::set_csmp(int fo, int vp, int pid, int dcs) { _at.lock(); _at.cmd_start("AT+CSMP="); _at.write_int(fo); _at.write_int(vp); _at.write_int(pid); _at.write_int(dcs); _at.cmd_stop(); _at.resp_start(); _at.resp_stop(); return _at.unlock_return_error(); } nsapi_error_t AT_CellularSMS::set_csdh(int show_header) { _at.lock(); _at.cmd_start("AT+CSDH="); _at.write_int(show_header); _at.cmd_stop(); _at.resp_start(); _at.resp_stop(); return _at.unlock_return_error(); } nsapi_error_t AT_CellularSMS::initialize(CellularSMSMmode mode) { _at.lock(); set_cnmi(); //set new SMS indication set_cmgf(mode); //set message format/PDU if (mode == CellularSMSMmodeText) { set_csmp(FIRST_OCTET_DELIVER_SUBMIT, TP_VALIDITY_PERIOD_24_HOURS, TP_PROTOCOL_IDENTIFIER, SMS_DATA_CODING_SCHEME); //set Set Text Mode Parameters(default values for SMS-SUBMIT and RECEIVE) set_csdh(1);//set header extra info as it's needed } _mode = mode; return _at.unlock_return_error(); } void AT_CellularSMS::set_extra_sim_wait_time(int sim_wait_time) { _sim_wait_time = sim_wait_time; } char* AT_CellularSMS::create_pdu(const char* phone_number, const char* message, uint8_t message_length, uint8_t msg_parts, uint8_t msg_part_number, uint8_t& header_size) { int totalPDULength = 0; int number_len = strlen(phone_number); totalPDULength += number_len; if (number_len&0x01) {// if phone number length is not even length we must pad it and so +1 totalPDULength += 1; } totalPDULength += 16; // all other than phone number and message length if (msg_parts > 1) {// add more space for UDH totalPDULength += 12; } // there might be need for padding so some more space totalPDULength +=2; // message 7-bit padded and it will be converted to hex so it will take twice as much space totalPDULength += (message_length - (message_length/8))*2; char* pdu = (char*)calloc(totalPDULength, sizeof(char)); if (!pdu) { return NULL; } int x = 0; // See more how to create PDU from 3GPP specification 23040 // first two define that we use service center number which is set with +CSCA pdu[x++] = '0'; pdu[x++] = '0'; // First Octet of the TPDU. 41 means SMS SUBMIT, no validity period, no status report, use User Data Header. // 01 means SMS SUBMIT, no validity period, no status report, NO User Data Header. if (msg_parts > 1) { // concatenated, must use UDH pdu[x++] = '4'; } else { pdu[x++] = '0'; } pdu[x++] = '1'; // assign a message reference automatically. We have defined TP-RD bit as 0 so duplicates are not rejected. pdu[x++] = '0'; pdu[x++] = '0'; // [6] and [7] Length of the Destination Phone Number int_to_hex_str(number_len, pdu+x); x+=2; // Type of the Destination Phone Number pdu[x++] = '8'; pdu[x++] = '1'; // phone number as reverse nibble encoded int i = 0; for (; i < number_len; i += 2) { if (i+1 == number_len) { pdu[x++] = 'f'; } else { pdu[x++] = phone_number[i+1]; } pdu[x++] = phone_number[i]; } // Protocol Identifier pdu[x++] = '0'; pdu[x++] = '0'; // Data Coding Scheme, GSM 7-bit default alphabet = '00', 8-bit '04' pdu[x++] = '0'; if (_use_8bit_encoding) { pdu[x++] = '4'; } else { pdu[x++] = '0'; } // possible to use 16 bit identifier, can't be defined yet from outside bool use_16_bit_identifier = false; uint8_t udhlen = 0; // Length can be update after we have created PDU, store position for later use. int lengthPos = x; x +=2; int paddingBits = 0; if (msg_parts > 1) { // concatenated, must use UDH // user data header length in chars pdu[x++] = '0'; if (use_16_bit_identifier) { udhlen = 7; // udh length in chars (6) + udhl length in chars pdu[x++] = '6'; } else { udhlen = 6; // udh length in chars (5) + udhl length in chars pdu[x++] = '5'; } // Information element identifier pdu[x++] = '0'; if (use_16_bit_identifier) { pdu[x++] = '8'; } else { pdu[x++] = '0'; } // Information element data length pdu[x++] = '0'; if (use_16_bit_identifier) { pdu[x++] = '4'; } else { pdu[x++] = '3'; } // A reference number (must be the same for all parts of the same larger messages) int_to_hex_str(_sms_message_ref_number&0xFF, pdu+x); x +=2; if (use_16_bit_identifier) { int_to_hex_str((_sms_message_ref_number>>16)&0xFF, pdu+x); x +=2; } // How many parts does this message have? int_to_hex_str(msg_parts, pdu+x); x +=2; // this is a part number int_to_hex_str(msg_part_number, pdu+x); x +=2; // if there is padding bits then udhlen is octet bigger as we need to keep septet boundary paddingBits = (udhlen * 8 ) % 7; if (paddingBits) { paddingBits = 7 - paddingBits; udhlen += 1; } } if (_use_8bit_encoding) { char_str_to_hex_str(message, message_length, pdu+x); } else { // we might need to send zero length sms if (message_length) { if (pack_7_bit_gsm_and_hex(message, message_length, pdu+x, paddingBits) == 0) { free(pdu); return NULL; } } } // now we know the correct length of the UDL (User Data Length) int_to_hex_str(message_length + udhlen, pdu+lengthPos); header_size = x; return pdu; } nsapi_size_or_error_t AT_CellularSMS::send_sms(const char* phone_number, const char* message, int msg_len) { int single_sms_max_length = _use_8bit_encoding ? SMS_MAX_SIZE_8BIT_SINGLE_SMS_SIZE : SMS_MAX_SIZE_GSM7_SINGLE_SMS_SIZE; if ((_mode == CellularSMSMmodeText && msg_len > single_sms_max_length) || !phone_number) { return NSAPI_ERROR_PARAMETER; } _at.lock(); int write_size = 0; int remove_plus_sign = (phone_number[0] == '+') ? 1 : 0; wait_ms(_sim_wait_time); if (_mode == CellularSMSMmodeText) { _at.cmd_start("AT+CMGS="); _at.write_string(phone_number+remove_plus_sign); _at.cmd_stop(); wait_ms(_sim_wait_time); _at.resp_start("> ", true); if (_at.get_last_error() == NSAPI_ERROR_OK) { write_size = _at.write_bytes((uint8_t*)message, msg_len); if (write_size < msg_len) { // sending can be cancelled by giving character (IRA 27). _at.cmd_start(ESC); _at.cmd_stop(); _at.unlock(); return write_size; } // (IRA 26) must be used to indicate the ending of the message body. _at.cmd_start(CTRL_Z); _at.cmd_stop(); _at.resp_start("+CMGS:"); _at.resp_stop(); } } else { // supports uncompressed 8 bit data and GSM 7 bit default alphabet data. Current implementation uses only // GSM 7 bit default but support is done for 8 bit data. int sms_count; int concatenated_sms_length = _use_8bit_encoding ? SMS_MAX_8BIT_CONCATENATED_SINGLE_SMS_SIZE : SMS_MAX_GSM7_CONCATENATED_SINGLE_SMS_SIZE; if (msg_len <= single_sms_max_length) { // single message sms_count = 1; } else { // concatenated message sms_count = msg_len/concatenated_sms_length; if (msg_len%concatenated_sms_length != 0) { sms_count++; } } int remaining_len = msg_len; int pdu_len; int msg_write_len = 0; uint8_t header_len; char *pdu_str; for (int i = 0; i < sms_count; i++) { header_len = 0; if (sms_count == 1) { pdu_len = msg_len; } else { pdu_len = remaining_len > concatenated_sms_length ? concatenated_sms_length : remaining_len; } pdu_str = create_pdu(phone_number+remove_plus_sign, message + i*concatenated_sms_length, pdu_len, sms_count, i+1, header_len); if (!pdu_str) { _at.unlock(); return NSAPI_ERROR_NO_MEMORY; } pdu_len = strlen(pdu_str); // specification says that service center number should not be included so we subtract -2 from pdu_len as we use '00' for automatic service center number _at.cmd_start("AT+CMGS="); _at.write_int((pdu_len-2)/2); _at.cmd_stop(); wait_ms(_sim_wait_time); _at.resp_start("> ", true); if (_at.get_last_error() == NSAPI_ERROR_OK) { write_size = _at.write_bytes((uint8_t*)pdu_str, pdu_len); if (write_size < pdu_len) { // calculate exact size of what we have send if (write_size <= header_len) { // managed only to write header or some of it so actual msg write size in this iteration is 0 write_size = 0; } else { write_size = (write_size - header_len)/2; // as hex encoded so divide by two } msg_write_len += write_size; // sending can be cancelled by giving character (IRA 27). _at.cmd_start(ESC); _at.cmd_stop(); _at.unlock(); free(pdu_str); return msg_write_len; } // (IRA 26) must be used to indicate the ending of the message body. _at.cmd_start(CTRL_Z); _at.cmd_stop(); _at.resp_start("+CMGS:"); _at.resp_stop(); } free(pdu_str); remaining_len -= concatenated_sms_length; if (_at.get_last_error() != NSAPI_ERROR_OK) { return _at.unlock_return_error(); } } } _sms_message_ref_number++; nsapi_error_t ret = _at.get_last_error(); _at.unlock(); return (ret == NSAPI_ERROR_OK) ? msg_len : ret; } void AT_CellularSMS::set_sms_callback(Callback func) { _cb = func; } nsapi_error_t AT_CellularSMS::set_cpms(const char *memr, const char *memw, const char *mems) { _at.lock(); _at.cmd_start("AT+CPMS="); _at.write_string(memr); _at.write_string(memw); _at.write_string(mems); _at.cmd_stop(); _at.resp_start(); _at.resp_stop(); return _at.unlock_return_error(); } nsapi_error_t AT_CellularSMS::set_csca(const char *sca, int type) { _at.lock(); _at.cmd_start("AT+CSCA="); _at.write_string(sca); _at.write_int(type); _at.cmd_stop(); _at.resp_start(); _at.resp_stop(); return _at.unlock_return_error(); } nsapi_size_or_error_t AT_CellularSMS::set_cscs(const char *chr_set) { _at.lock(); _at.cmd_start("AT+CSCS="); _at.write_string(chr_set); _at.cmd_stop(); _at.resp_start(); _at.resp_stop(); return _at.unlock_return_error(); } nsapi_error_t AT_CellularSMS::delete_sms(sms_info_t* sms) { _at.lock(); for (int i = 0; i < sms->parts; i++) { _at.cmd_start("AT+CMGD="); _at.write_int(sms->msg_index[i]); _at.cmd_stop(); _at.resp_start(); _at.resp_stop(); } return _at.unlock_return_error(); } // we need this as for example concatenated sms can get different sms reference numbers // if for example last part take much more time to arrive. This situation happened while testing. // What this means that after this we can't read another sms because we always read the oldest sms // that was corrupted. So we need to have delete all messages. nsapi_error_t AT_CellularSMS::delete_all_messages() { _at.lock(); _at.cmd_start("AT+CMGD=1,4"); _at.cmd_stop(); _at.resp_start(); _at.resp_stop(); return _at.unlock_return_error(); } // read msg in text mode nsapi_size_or_error_t AT_CellularSMS::read_sms_from_index(int msg_index, char* buf, uint16_t len, char* phone_num, char* time_stamp) { /* * +CMGR: ,,,[,,,,,,,]OK */ wait_ms(_sim_wait_time); _at.cmd_start("AT+CMGR="); _at.write_int(msg_index); _at.cmd_stop(); // TODO: NOTE: If the selected can contain different types of SMs (e.g. SMS-DELIVERs, SMS-SUBMITs, SMS-STATUS-REPORTs and SMS-COMMANDs), // the response may be a mix of the responses of different SM types. TE application can recognize the response format by examining the third response parameter. // for now we support sms reading of received messages int buf_len = 0; if (_at.get_last_error() == NSAPI_ERROR_OK) { char status[SMS_STATUS_SIZE]; // first we read msg status and with that we can decide how the rest of message format _at.resp_start("+CMGR:"); if (_at.info_resp()) { _at.read_string(status, SMS_STATUS_SIZE); uint16_t status_len = strlen(status); if (((status_len == sizeof("REC READ") - 1) && memcmp("REC READ", status, status_len) == 0) || ((status_len == sizeof("REC UNREAD") - 1) && memcmp("REC UNREAD", status, status_len) == 0)) { // Received message if (phone_num) { _at.read_string(phone_num, SMS_MAX_PHONE_NUMBER_SIZE); } else { _at.skip_param(); // , } _at.skip_param(); // if (time_stamp) { _at.read_string(time_stamp, SMS_MAX_TIME_STAMP_SIZE); } (void)_at.consume_to_stop_tag(); // consume until if (buf) { _at.read_string(buf, len); buf_len = strlen(buf); } } } _at.resp_stop(); } return (_at.get_last_error() == NSAPI_ERROR_OK) ? buf_len : _at.get_last_error(); } // read msg in PDU mode nsapi_size_or_error_t AT_CellularSMS::read_sms(sms_info_t* sms, char* buf, char* phone_num, char* time_stamp) { // +CMGR: ,[], int index = -1; if (sms->parts == sms->parts_added) { char *pdu; // we need a temp buffer as payload is hexencoded ---> can't use buf as it might be enough for message but not hexenconded pdu. int status = -1; int msg_len = 0; index = 0; int pduSize = 0; for (int i = 0; i < sms->parts; i++) { wait_ms(_sim_wait_time); _at.cmd_start("AT+CMGR="); _at.write_int(sms->msg_index[i]); _at.cmd_stop(); _at.resp_start("+CMGR:"); if (_at.info_resp()) { status = _at.read_int(); _at.skip_param(); // if ((_at.get_last_error() == NSAPI_ERROR_OK) && (status == 0 || status == 1)) { msg_len = _at.read_int(); if (msg_len > 0) { pduSize = msg_len*2 + 20;// *2 as it's hex encoded and +20 as service center number is not included in size given by CMGR pdu = (char*)calloc(pduSize, sizeof(char)); if (!pdu) { _at.resp_stop(); return NSAPI_ERROR_NO_MEMORY; } _at.read_string(pdu, pduSize, true); if (_at.get_last_error() == NSAPI_ERROR_OK) { msg_len = get_data_from_pdu(pdu, NULL, NULL, phone_num, buf+index); if (msg_len >= 0) { // we need to allow zero length messages index += msg_len; } else { free(pdu); _at.resp_stop(); return -1; } } free(pdu); } } } _at.resp_stop(); } if (_at.get_last_error() == NSAPI_ERROR_OK) { if (time_stamp) { strcpy(time_stamp, sms->date); } buf[index] = '\0'; } } else { tr_warn("NOT all concatenated parts were received..."); index = SMS_ERROR_MULTIPART_ALL_PARTS_NOT_READ; } return index; } nsapi_size_or_error_t AT_CellularSMS::get_sms(char* buf, uint16_t len, char* phone_num, uint16_t phone_len, char* time_stamp, uint16_t time_len, int *buf_size) { // validate buffer sizes already here to avoid any necessary function calls and locking of _at if ((phone_num && phone_len < SMS_MAX_PHONE_NUMBER_SIZE) || (time_stamp && time_len < SMS_MAX_TIME_STAMP_SIZE) || buf == NULL) { return NSAPI_ERROR_PARAMETER; } _at.lock(); nsapi_size_or_error_t err = list_messages(); if (err == NSAPI_ERROR_OK) { // we return the oldest sms and delete it after successful read sms_info_t* info = get_oldest_sms_index(); if (info) { if (info->msg_size+1 > len) { // +1 for '\0' tr_warn("Given buf too small, len is: %d but is must be: %d", len, info->msg_size); if (buf_size) { *buf_size = info->msg_size; } free_linked_list(); _at.unlock(); return NSAPI_ERROR_PARAMETER; } if (_mode == CellularSMSMmodePDU) { err = read_sms(info, buf, phone_num, time_stamp); } else { err = read_sms_from_index(info->msg_index[0], buf, len, phone_num, time_stamp); } if (err > 0) { int delerr = delete_sms(info); if (delerr) { err = delerr; } } } else { // No messages were found, return -1 err = -1; } } free_linked_list(); _at.unlock(); // update error only when there really was an error, otherwise we return the length if (_at.get_last_error()) { err = _at.get_last_error(); } return err; } nsapi_size_or_error_t AT_CellularSMS::get_data_from_pdu(const char* pdu, sms_info_t *info, int *part_number, char *phone_number, char *msg) { int index = 0; int tmp; bool userDataHeader; int oaLength; int dataScheme; nsapi_size_or_error_t err = NSAPI_ERROR_OK; // read Length of the SMSC information oaLength = hex_str_to_int(pdu, 2); index += 2; // length we just read index += oaLength*2; // skip service center number // read first the lower part of first octet as there is message type index++; tmp = hex_str_to_int(pdu+index, 1); //wait_ms(200); if ((tmp & 0x03) == 0) {// SMS-DELIVER type, last two bits should be zero // UDH present? Check from first octets higher part tmp = hex_str_to_int(pdu + (--index), 1); userDataHeader = ((tmp & 0x04) == 0) ? false : true; index +=2; // we just read the high bits of first octet so move +2 // originating address length oaLength = hex_str_to_int(pdu+index, 2); index +=2; // add index over address length index +=2; // skip number type if (phone_number) { // phone number as reverse nibble encoded int a = 0; for (; a < oaLength; a +=2) { if (a+1 == oaLength) { phone_number[a] = pdu[index+a+1]; } else { phone_number[a] = pdu[index+a+1]; phone_number[a+1] = pdu[index+a]; } } phone_number[oaLength] = '\0'; } index += oaLength; if (oaLength&0x01) { // if phone number length is odd then it has padded F so skip that index++; } index +=2; // skip TP-Protocol identifier dataScheme = hex_str_to_int(pdu+index, 2); index +=2; // skip TP-Data-Coding-Scheme // next one is date, it's length is 7 octets according to 3GPP TS 23.040 // create time string if (info) { int i = 0; // year info->date[i++] = pdu[index+1]; info->date[i++] = pdu[index]; index+=2; info->date[i++] = '/'; // month info->date[i++] = pdu[index+1]; info->date[i++] = pdu[index]; index+=2; info->date[i++] = '/'; // Day info->date[i++] = pdu[index+1]; info->date[i++] = pdu[index]; index+=2; info->date[i++] = ','; // Hour info->date[i++] = pdu[index+1]; info->date[i++] = pdu[index]; index+=2; info->date[i++] = ':'; // Minute info->date[i++] = pdu[index+1]; info->date[i++] = pdu[index]; index+=2; info->date[i++] = ':'; // Second info->date[i++] = pdu[index+1]; info->date[i++] = pdu[index]; index+=2; // timezone related to GMT. pdu[index+1] most significant bit indicates the sign related to gmt tmp = hex_str_to_int(pdu+index+1, 1); if (tmp&0x08) { info->date[i++] = '-'; } else { info->date[i++] = '+'; } // pdu[index+1 & 0x07 is the most significant bits of the timezone // pdu [index] is the least significant bits info->date[i++] = '0' + (tmp & 0x07); info->date[i++] = pdu[index]; info->date[i] = '\0'; index+=2; } else { index+=14; } int udl = hex_str_to_int(pdu+index, 2); index +=2; int paddingBits = 0; int partnro = 1; if (userDataHeader) { // we need to read User Defined Header to know what part number this message is. index += read_udh_from_pdu(pdu+index, info, partnro, paddingBits); } if (part_number) { *part_number = partnro; } if (msg) { // we are reading the message err = read_pdu_payload(pdu+index, udl, dataScheme, msg, paddingBits); } else { if (dataScheme == 0x00) { // when listing messages we need to calculated length. Other way would be unpacking the whole message. err = strlen(pdu+index) >> 1; err *= 8; err /= 7; } else if (dataScheme == 0x04) { err = strlen(pdu+index) >> 1; } else { return NSAPI_ERROR_UNSUPPORTED; } } return err; } else { // message was not DELIVER so discard it return NSAPI_ERROR_UNSUPPORTED; } } // read params from User Defined Header int AT_CellularSMS::read_udh_from_pdu(const char* pdu, sms_info_t *info, int &part_number, int &padding_bits) { int index = 0; int udhLength = hex_str_to_int(pdu, 2); index +=2; // if there is padding bits then udhlen is octet bigger as we need to keep septet boundary padding_bits = ((udhLength+1) * 8 ) % 7; // +1 is for udhLength itself if (padding_bits) { padding_bits = 7 - padding_bits; } else { padding_bits = 0; } int tmp = hex_str_to_int(pdu+index, 2); index +=4; if (tmp == 0) { // 8-bit reference number if (info) { info->msg_ref_number = (uint16_t)hex_str_to_int(pdu+index, 2); } index +=2; } else { // 16-bit reference number if (info) { info->msg_ref_number = (uint16_t)hex_str_to_int(pdu+index+2, 2); tmp = hex_str_to_int(pdu+index, 2); info->msg_ref_number |= (tmp << 8); } index +=4; } if (info) { info->parts = hex_str_to_int(pdu+index, 2); } index +=2; part_number = hex_str_to_int(pdu+index, 2); index +=2; return (udhLength*2 + 2); // udh in hex and udhl } nsapi_size_or_error_t AT_CellularSMS::read_pdu_payload(const char* pdu, int msg_len, int scheme, char *msg, int padding_bits) { if (scheme == 0x00) { // 7 bit gsm encoding, must do the conversions from hex to 7-bit encoding and to ascii return unpack_7_bit_gsm_to_str(pdu, strlen(pdu)/2, msg, padding_bits, msg_len); } else if (scheme == 0x04) { // 8bit scheme so just convert hexstring to charstring return hex_str_to_char_str(pdu, strlen(pdu), msg); } else { tr_error("Received unsupported data coding scheme: 0x%02x", scheme); return NSAPI_ERROR_UNSUPPORTED; } } void AT_CellularSMS::free_linked_list() { sms_info_t* info = _sms_info; sms_info_t* old; while (info) { old = info; info = info->next_info; delete old; } _sms_info = NULL; } void AT_CellularSMS::add_info(sms_info_t* info, int index, int part_number) { // check for same message reference id. If found, update it and delete the given info. // if NOT found then add to the end of the list. if (!_sms_info) { info->msg_index[part_number-1] = index; // part numbering starts from 1 so -1 to put to right index _sms_info = info; return; } sms_info_t* current = _sms_info; sms_info_t* prev; bool found_msg = false; while (current) { prev = current; // sms messages can have same reference number so additional checks are needed. // TODO: should we include phone number also? if (current->msg_ref_number == info->msg_ref_number && current->parts > current->parts_added && info->parts > info->parts_added) { // multipart sms, update msg size and index current->msg_size += info->msg_size; current->msg_index[part_number-1] = index; // part numbering starts from 1 so -1 to put to right index current->parts_added++; // update oldest part as date if (compare_time_strings(info->date, current->date) == -1) { strcpy(current->date, info->date); } found_msg = true; break; } current = current->next_info; } if (found_msg) { // info was added to existing item in linked list, must be deleted delete info; } else { // message not found, add to linked list info->msg_index[part_number-1] = index; prev->next_info = info; } } // reads all the messages to the linked list AT_CellularSMS::_sms_info nsapi_error_t AT_CellularSMS::list_messages() { // TODO: NOTE: If the selected can contain different types of SMs (e.g. SMS-DELIVERs, SMS-SUBMITs, SMS-STATUS-REPORTs and SMS-COMMANDs), // the response may be a mix of the responses of different SM types. TE application can recognize the response format by examining the third response parameter. // for now we assume that only SMS-DELIVER messages are read. if (_mode == CellularSMSMmodePDU) { _at.cmd_start("AT+CMGL=4"); } else { _at.cmd_start("AT+CMGL=\"ALL\""); } _at.cmd_stop(); sms_info_t* info = NULL; // init for 1 so that in text mode we will add to the correct place without any additional logic in addInfo() in text mode int part_number = 1; int index = 0; int length = 0; char *pdu = NULL; _at.resp_start("+CMGL:"); while (_at.info_resp()) { info = new sms_info_t(); if (!info) { _at.resp_stop(); return NSAPI_ERROR_NO_MEMORY; } if (_mode == CellularSMSMmodePDU) { //+CMGL: ,,[],[ // +CMGL:,,[], //[...]] index = _at.read_int(); _at.skip_param(2); // ,[] length = _at.read_int(); length = length*2 + 20;// *2 as it's hex encoded and +20 as service center number is not included in size given by CMGL pdu = (char*)calloc(length, sizeof(char)); if (!pdu) { delete info; _at.resp_stop(); return NSAPI_ERROR_NO_MEMORY; } _at.read_string(pdu, length, true); if (_at.get_last_error() == NSAPI_ERROR_OK) { info->msg_size = get_data_from_pdu(pdu, info, &part_number); } } else { // +CMGL: ,,,[],[][,,][ // +CMGL: ,,,[],[][,,][...]] index = _at.read_int(); (void)_at.consume_to_stop_tag(); // consume until (void)_at.consume_to_stop_tag(); // consume until } if (index > 0) { add_info(info, index, part_number); } else { delete info; info = NULL; } free(pdu); pdu = NULL; } _at.resp_stop(); return _at.get_last_error(); } AT_CellularSMS::sms_info_t* AT_CellularSMS::get_oldest_sms_index() { /* * Different scenarios when finding the oldest concatenated sms * * 1. Find first parts first and it was received first * 2. Find first parts first and it was NOT received first -> older timestamp might exist in some other part * 3. Find other than first part first and it was received first * 4. Find other than first part first and it was NOT received first -> older timestamp might exist in some other part * * So must take all message to a linked list and loop that for the oldest */ // if text mode we need to read sms with +CMGR because time stamp is optional while looping with +CMGL sms_info_t* retVal = NULL; sms_info_t* current = _sms_info; nsapi_size_or_error_t err = 0; while (current) { if (_mode == CellularSMSMmodeText) { wait_ms(_sim_wait_time); err = read_sms_from_index(current->msg_index[0], NULL, 0, NULL, current->date); if (err != 0) { return NULL; } } if (retVal == NULL) { retVal = current; } else if (compare_time_strings(current->date, retVal->date) == -1) { // found older sms, update return value to oldest retVal = current; } current = current->next_info; } return retVal; } // if time_string_1 is greater (more fresh date) then return 1, same 0, smaller -1. Error -2 int AT_CellularSMS::compare_time_strings(const char* time_string_1, const char* time_string_2) { time_t t1; time_t t2; bool success = create_time(time_string_1, &t1) && create_time(time_string_2, &t2); int retVal = -2; if (success) { double diff = difftime(t1, t2); if (diff > 0) { retVal = 1; } else if (diff == 0) { retVal = 0; } else { retVal = -1; } } return retVal; } bool AT_CellularSMS::create_time(const char* time_string, time_t* time) { const int kNumberOfElements = 8; tm time_struct = { 0 }; int gmt = 0; char sign; bool retVal = false; if (sscanf(time_string, "%d/%d/%d,%d:%d:%d%c%d", &time_struct.tm_year, &time_struct.tm_mon, &time_struct.tm_mday, &time_struct.tm_hour, &time_struct.tm_min, &time_struct.tm_sec, &sign, &gmt) == kNumberOfElements) { *time = mktime(&time_struct); // add timezone as seconds. gmt is in quarter of hours. int x = 60 * 60 * gmt * 0.25; if (sign == '+') { *time += x; } else { *time -= x; } retVal = true; } return retVal; } uint16_t AT_CellularSMS::pack_7_bit_gsm_and_hex(const char* str, uint16_t len, char *buf, int number_of_padding_bit) { uint16_t strCnt = 0; uint16_t i = 0; uint8_t shift; char tmp; // convert to 7bit gsm first char* gsm_str = (char*)malloc(len); if (!gsm_str) { return 0; } for (uint16_t y = 0; y < len; y++) { for (int x=0; x < GSM_TO_ASCII_TABLE_SIZE; x++) { if (gsm_to_ascii[x] == str[y]) { gsm_str[y] = x; } } } // then packing and converting to hex if (number_of_padding_bit) { tmp = gsm_str[strCnt]<>shift); } else { tmp = (gsm_str[strCnt]>>shift) | (gsm_str[strCnt+1] <<(7-shift)); } char_str_to_hex_str(&tmp, 1, buf+(i*2)); if (shift == 6) { strCnt++; } strCnt++; i++; } free(gsm_str); return i; } uint16_t AT_CellularSMS::unpack_7_bit_gsm_to_str(const char* str, int len, char *buf, int padding_bits, int msg_len) { int strCount = 0; uint16_t decodedCount = 0; uint8_t shift; char tmp; char tmp1; if (padding_bits) { hex_str_to_char_str(str, 2, &tmp); buf[decodedCount] = gsm_to_ascii[(tmp>>padding_bits) & 0x7F]; strCount++; decodedCount++; } while (strCount < len) { shift = (strCount-padding_bits)%7; hex_str_to_char_str(str + strCount*2, 2, &tmp); if (shift == 0) { buf[decodedCount] = gsm_to_ascii[tmp & 0x7F]; } else if (shift == 6) { hex_str_to_char_str(str + (strCount-1)*2, 2, &tmp1); buf[decodedCount] = gsm_to_ascii[(((tmp1>>2)) | (tmp << 6)) & 0x7F]; if (decodedCount+1 < msg_len) { hex_str_to_char_str(str + strCount*2, 2, &tmp); decodedCount++; buf[decodedCount] = gsm_to_ascii[(tmp>>1) & 0x7F]; } } else { hex_str_to_char_str(str + (strCount-1)*2, 2, &tmp1); buf[decodedCount] = gsm_to_ascii[(((tmp1>>(8- shift))) | ((tmp << shift))) & 0x7F]; } strCount++; decodedCount++; } return decodedCount; }